// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Find broken links in tree // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2021 by Wilson Snyder. This program is free software; you // can redistribute it and/or modify it under the terms of either the GNU // Lesser General Public License Version 3 or the Perl Artistic License // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // V3Broken's Transformations: // // Entire netlist // Mark all nodes // Check all links point to marked nodes // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include "V3Global.h" #include "V3Broken.h" #include "V3Ast.h" // This visitor does not edit nodes, and is called at error-exit, so should use constant iterators #include "V3AstConstOnly.h" #include #include //###################################################################### class BrokenTable VL_NOT_FINAL : public AstNVisitor { // Table of brokenExists node pointers private: // MEMBERS // For each node, we keep if it exists or not. using NodeMap = std::unordered_map; // Performance matters (when --debug) static NodeMap s_nodes; // Set of all nodes that exist // BITMASK enum { FLAG_ALLOCATED = 0x01 }; // new() and not delete()ed enum { FLAG_IN_TREE = 0x02 }; // Is in netlist tree enum { FLAG_LINKABLE = 0x04 }; // Is in netlist tree, can be linked to enum { FLAG_LEAKED = 0x08 }; // Known to have been leaked enum { FLAG_UNDER_NOW = 0x10 }; // Is in tree as parent of current node public: // METHODS static void deleted(const AstNode* nodep) { // Called by operator delete on any node - only if VL_LEAK_CHECKS if (debug() >= 9) cout << "-nodeDel: " << cvtToHex(nodep) << endl; const auto iter = s_nodes.find(nodep); UASSERT_OBJ(!(iter == s_nodes.end() || !(iter->second & FLAG_ALLOCATED)), reinterpret_cast(nodep), "Deleting AstNode object that was never tracked or already deleted"); if (iter != s_nodes.end()) s_nodes.erase(iter); } #if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 4 // GCC 4.4.* compiler warning bug, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39390 #pragma GCC diagnostic ignored "-Wstrict-aliasing" #endif static void addNewed(const AstNode* nodep) { // Called by operator new on any node - only if VL_LEAK_CHECKS if (debug() >= 9) cout << "-nodeNew: " << cvtToHex(nodep) << endl; const auto iter = s_nodes.find(nodep); UASSERT_OBJ(!(iter != s_nodes.end() && (iter->second & FLAG_ALLOCATED)), nodep, "Newing AstNode object that is already allocated"); if (iter == s_nodes.end()) { int flags = FLAG_ALLOCATED; // This int needed to appease GCC 4.1.2 s_nodes.emplace(nodep, flags); } } static void setUnder(const AstNode* nodep, bool flag) { // Called by BrokenCheckVisitor when each node entered/exited if (!okIfLinkedTo(nodep)) return; const auto iter = s_nodes.find(nodep); if (iter != s_nodes.end()) { iter->second &= ~FLAG_UNDER_NOW; if (flag) iter->second |= FLAG_UNDER_NOW; } } static void addInTree(AstNode* nodep, bool linkable) { #ifndef VL_LEAK_CHECKS // cppcheck-suppress knownConditionTrueFalse if (!linkable) return; // save some time, else the map will get huge! #endif const auto iter = s_nodes.find(nodep); if (VL_UNCOVERABLE(iter == s_nodes.end())) { #ifdef VL_LEAK_CHECKS nodep->v3fatalSrc("AstNode is in tree, but not allocated"); #endif } else { #ifdef VL_LEAK_CHECKS UASSERT_OBJ(iter->second & FLAG_ALLOCATED, nodep, "AstNode is in tree, but not allocated"); #endif UASSERT_OBJ(!(iter->second & FLAG_IN_TREE), nodep, "AstNode is already in tree at another location"); } int or_flags = FLAG_IN_TREE | (linkable ? FLAG_LINKABLE : 0); if (iter == s_nodes.end()) { s_nodes.emplace(nodep, or_flags); } else { iter->second |= or_flags; } } static bool isAllocated(const AstNode* nodep) { // Some generic node has a pointer to this node. Is it allocated? // Use this when might not be in tree; otherwise use okIfLinkedTo(). #ifdef VL_LEAK_CHECKS const auto iter = s_nodes.find(nodep); if (iter == s_nodes.end()) return false; if (!(iter->second & FLAG_ALLOCATED)) return false; #endif return true; } static bool okIfLinkedTo(const AstNode* nodep) { // Some node in tree has a pointer to this node. Is it kosher? const auto iter = s_nodes.find(nodep); if (iter == s_nodes.end()) return false; #ifdef VL_LEAK_CHECKS if (!(iter->second & FLAG_ALLOCATED)) return false; #endif if (!(iter->second & FLAG_IN_TREE)) return false; if (!(iter->second & FLAG_LINKABLE)) return false; return true; } static bool okIfAbove(const AstNode* nodep) { // Must be linked to and below current node if (!okIfLinkedTo(nodep)) return false; const auto iter = s_nodes.find(nodep); if (iter == s_nodes.end()) return false; if ((iter->second & FLAG_UNDER_NOW)) return false; return true; } static bool okIfBelow(const AstNode* nodep) { // Must be linked to and below current node if (!okIfLinkedTo(nodep)) return false; const auto iter = s_nodes.find(nodep); if (iter == s_nodes.end()) return false; if (!(iter->second & FLAG_UNDER_NOW)) return false; return true; } static void prepForTree() { #ifndef VL_LEAK_CHECKS s_nodes.clear(); #else for (NodeMap::iterator it = s_nodes.begin(); it != s_nodes.end(); ++it) { it->second &= ~FLAG_IN_TREE; it->second &= ~FLAG_LINKABLE; } #endif } static void doneWithTree() { for (int backs = 0; backs < 2; backs++) { // Those with backp() are probably under one leaking without for (NodeMap::iterator it = s_nodes.begin(); it != s_nodes.end(); ++it) { // LCOV_EXCL_START if (VL_UNCOVERABLE((it->second & FLAG_ALLOCATED) && !(it->second & FLAG_IN_TREE) && !(it->second & FLAG_LEAKED) && (it->first->backp() ? backs == 1 : backs == 0))) { // Use only AstNode::dump instead of the virtual one, as there // may be varp() and other cross links that are bad. if (v3Global.opt.debugCheck()) { // When get this message, find what forgot to delete the // node by running GDB, where for node "" use: // watch AstNode::s_editCntGbl==#### // run // bt std::cerr << "%Error: LeakedNode" << (it->first->backp() ? "Back: " : ": "); AstNode* rawp = const_cast(static_cast(it->first)); rawp->AstNode::dump(std::cerr); std::cerr << endl; V3Error::incErrors(); } it->second |= FLAG_LEAKED; } // LCOV_EXCL_STOP } } } // CONSTRUCTORS BrokenTable() = default; virtual ~BrokenTable() override = default; }; BrokenTable::NodeMap BrokenTable::s_nodes; bool AstNode::brokeExists() const { // Called by node->broken() routines to do table lookup return BrokenTable::okIfLinkedTo(this); } bool AstNode::brokeExistsAbove() const { // Called by node->broken() routines to do table lookup return BrokenTable::okIfBelow(this); } bool AstNode::brokeExistsBelow() const { // Called by node->broken() routines to do table lookup return BrokenTable::okIfAbove(this); } //###################################################################### class BrokenMarkVisitor final : public AstNVisitor { // Mark every node in the tree private: // NODE STATE // Nothing! // This may be called deep inside other routines // // so userp and friends may not be used // METHODS void processAndIterate(AstNode* nodep) { BrokenTable::addInTree(nodep, nodep->maybePointedTo()); iterateChildrenConst(nodep); } // VISITORS virtual void visit(AstNode* nodep) override { // Process not just iterate processAndIterate(nodep); } public: // CONSTRUCTORS explicit BrokenMarkVisitor(AstNetlist* nodep) { iterate(nodep); } virtual ~BrokenMarkVisitor() override = default; }; //###################################################################### // Broken state, as a visitor of each AstNode class BrokenCheckVisitor final : public AstNVisitor { bool m_inScope = false; // Under AstScope private: static void checkWidthMin(const AstNode* nodep) { UASSERT_OBJ(nodep->width() == nodep->widthMin() || v3Global.widthMinUsage() != VWidthMinUsage::MATCHES_WIDTH, nodep, "Width != WidthMin"); } void processAndIterate(AstNode* nodep) { BrokenTable::setUnder(nodep, true); const char* whyp = nodep->broken(); UASSERT_OBJ(!whyp, nodep, "Broken link in node (or something without maybePointedTo): " << whyp); if (nodep->dtypep()) { UASSERT_OBJ(nodep->dtypep()->brokeExists(), nodep, "Broken link in node->dtypep() to " << cvtToHex(nodep->dtypep())); UASSERT_OBJ(VN_IS(nodep->dtypep(), NodeDType), nodep, "Non-dtype link in node->dtypep() to " << cvtToHex(nodep->dtypep())); } if (v3Global.assertDTypesResolved()) { if (nodep->hasDType()) { UASSERT_OBJ(nodep->dtypep(), nodep, "No dtype on node with hasDType(): " << nodep->prettyTypeName()); } else { UASSERT_OBJ(!nodep->dtypep(), nodep, "DType on node without hasDType(): " << nodep->prettyTypeName()); } UASSERT_OBJ(!nodep->getChildDTypep(), nodep, "childDTypep() non-null on node after should have removed"); if (const AstNodeDType* dnodep = VN_CAST(nodep, NodeDType)) checkWidthMin(dnodep); } checkWidthMin(nodep); iterateChildrenConst(nodep); BrokenTable::setUnder(nodep, false); } virtual void visit(AstNodeAssign* nodep) override { processAndIterate(nodep); UASSERT_OBJ(!(v3Global.assertDTypesResolved() && nodep->brokeLhsMustBeLvalue() && VN_IS(nodep->lhsp(), NodeVarRef) && !VN_CAST(nodep->lhsp(), NodeVarRef)->access().isWriteOrRW()), nodep, "Assignment LHS is not an lvalue"); } virtual void visit(AstScope* nodep) override { VL_RESTORER(m_inScope); { m_inScope = true; processAndIterate(nodep); } } virtual void visit(AstNodeVarRef* nodep) override { processAndIterate(nodep); // m_inScope because some Vars have initial variable references without scopes // This might false fire with some debug flags, as not certain we don't have temporary // clear varScopep's during some an infrequent dump just before we re-LinkDot. UASSERT_OBJ( !(v3Global.assertScoped() && m_inScope && nodep->varp() && !nodep->varScopep()), nodep, "VarRef missing VarScope pointer"); } virtual void visit(AstNode* nodep) override { // Process not just iterate processAndIterate(nodep); } public: // CONSTRUCTORS explicit BrokenCheckVisitor(AstNetlist* nodep) { iterate(nodep); } virtual ~BrokenCheckVisitor() override = default; }; //###################################################################### // Broken class functions void V3Broken::brokenAll(AstNetlist* nodep) { // UINFO(9, __FUNCTION__ << ": " << endl); static bool inBroken = false; if (VL_UNCOVERABLE(inBroken)) { // A error called by broken can recurse back into broken; avoid this UINFO(1, "Broken called under broken, skipping recursion.\n"); // LCOV_EXCL_LINE } else { inBroken = true; BrokenTable::prepForTree(); BrokenMarkVisitor mvisitor(nodep); BrokenCheckVisitor cvisitor(nodep); BrokenTable::doneWithTree(); inBroken = false; } } void V3Broken::addNewed(AstNode* nodep) { BrokenTable::addNewed(nodep); } void V3Broken::deleted(AstNode* nodep) { BrokenTable::deleted(nodep); } bool V3Broken::isAllocated(AstNode* nodep) { return BrokenTable::isAllocated(nodep); } void V3Broken::selfTest() { // Warmup addNewed and deleted for coverage, as otherwise only with VL_LEAK_CHECKS FileLine* fl = new FileLine(FileLine::commandLineFilename()); auto* newp = new AstBegin(fl, "[EditWrapper]", nullptr); #ifndef VL_LEAK_CHECKS addNewed(newp); deleted(newp); #endif VL_DO_DANGLING(delete newp, newp); }