//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1999. // // File: N E T C F G . C P P // // Contents: The main set of routines for dealing with the network // binding engine. // // Notes: // // Author: shaunco 15 Jan 1999 // //---------------------------------------------------------------------------- #include #pragma hdrstop #include "diagctx.h" #include "inetcfg.h" #include "persist.h" #include "netcfg.h" #include "util.h" // Called after a component has been removed. In case this component // is still listed in any other component's references, we remove those // references. This can happen if a notify object installs something // on behalf of its component, but forgets to remove it. // VOID CNetConfigCore::EnsureComponentNotReferencedByOthers ( IN const CComponent* pComponent) { TraceFileFunc(ttidNetcfgBase); CComponentList::iterator iter; CComponent* pScan; for (iter = Components.begin(); iter != Components.end(); iter++) { pScan = *iter; Assert (pScan); if (pScan->Refs.FIsReferencedByComponent (pComponent)) { g_pDiagCtx->Printf (ttidBeDiag, " %S is still referenced by %S. " "Removing the refernce.\n", pScan->PszGetPnpIdOrInfId(), pComponent->PszGetPnpIdOrInfId()); pScan->Refs.RemoveReferenceByComponent (pComponent); } } } HRESULT CNetConfigCore::HrCopyCore ( IN const CNetConfigCore* pSourceCore) { TraceFileFunc(ttidNetcfgBase); HRESULT hr; hr = Components.HrCopyComponentList (&pSourceCore->Components); if (S_OK != hr) { goto finished; } hr = StackTable.HrCopyStackTable (&pSourceCore->StackTable); if (S_OK != hr) { goto finished; } hr = DisabledBindings.HrCopyBindingSet (&pSourceCore->DisabledBindings); if (S_OK != hr) { goto finished; } finished: if (S_OK != hr) { Clear (); } TraceHr (ttidError, FAL, hr, FALSE, "CNetConfigCore::HrCopyCore"); return hr; } VOID CNetConfigCore::Clear () { TraceFileFunc(ttidNetcfgBase); Assert (this); DisabledBindings.Clear(); StackTable.Clear(); Components.Clear(); } VOID CNetConfigCore::Free () { TraceFileFunc(ttidNetcfgBase); Assert (this); FreeCollectionAndItem (Components); } BOOL CNetConfigCore::FIsEmpty () const { TraceFileFunc(ttidNetcfgBase); return Components.FIsEmpty () && StackTable.FIsEmpty () && DisabledBindings.FIsEmpty (); } BOOL CNetConfigCore::FContainsFilterComponent () const { TraceFileFunc(ttidNetcfgBase); CComponentList::const_iterator iter; const CComponent* pComponent; for (iter = Components.begin(); iter != Components.end(); iter++) { pComponent = *iter; Assert (pComponent); if (pComponent->FIsFilter()) { return TRUE; } } return FALSE; } BOOL CNetConfigCore::FIsBindPathDisabled ( IN const CBindPath* pBindPath, IN DWORD dwFlags /* IBD_FLAGS */) const { TraceFileFunc(ttidNetcfgBase); const CBindPath* pScan; Assert (this); Assert (pBindPath); Assert ((dwFlags & IBD_EXACT_MATCH_ONLY) || (dwFlags & IBD_MATCH_SUBPATHS_TOO)); DbgVerifyBindingSet (&DisabledBindings); // The bindpath is disabled if it matches one of the bindpaths in // the disabled set or if one of the bindpaths in the disabled set // is a subpath of it. // // IBD_EXACT_MATCH_ONLY is used when writing bindings to the registry. // We only neglect writing a disabled binding for the components // which directly have the disabled binding. i.e. if // netbt>-tcpip->adapter is disabled, we only negect to write the // binding for netbt. We still write binding for components above // netbt (server, client) as if they were bound. We do this because // 1) it doesn't matter that the upper components have these written // 2) it means we don't have to involve these upper components in // PnP notifications when then binding is enabled/disabled. // // IBD_MATCH_SUBPATHS_TOO is used when reporting bindpaths as enabled // or disabled through the INetCfg interface. To clients, a binding // is disabled if any of its subpaths are disabled. It has to be // because there is no way that connectivity along the entire path // will happen if any of the subpaths are cut-off (disabled). // for (pScan = DisabledBindings.begin(); pScan != DisabledBindings.end(); pScan++) { if (pScan->FIsSameBindPathAs (pBindPath)) { return TRUE; } if (dwFlags & IBD_MATCH_SUBPATHS_TOO) { if (pScan->FIsSubPathOf (pBindPath)) { return TRUE; } } } return FALSE; } // An alternate way to check if a bindpath is disabled when you have // just the two components in question and you know that they are expected // to be bound to one another. Using this method as opposed to // FIsBindPathDisabled is better when you don't have the binpath allocated // and don't want to allocate one just to check it. Thus, this method // allocates no memory and doesn't need to check subpaths because a bindpath // of length 2 can have no subpaths. This method is used primarily when // seeing if a filter is bound to an adapter. // BOOL CNetConfigCore::FIsLength2BindPathDisabled ( IN const CComponent* pUpper, IN const CComponent* pLower) const { TraceFileFunc(ttidNetcfgBase); const CBindPath* pScan; Assert (this); Assert (pUpper); Assert (pLower); DbgVerifyBindingSet (&DisabledBindings); for (pScan = DisabledBindings.begin(); pScan != DisabledBindings.end(); pScan++) { if (pScan->CountComponents() != 2) { continue; } if ((pScan->POwner() == pUpper) && (pScan->PLastComponent() == pLower)) { return TRUE; } } return FALSE; } HRESULT CNetConfigCore::HrDisableBindPath ( IN const CBindPath* pBindPath) { TraceFileFunc(ttidNetcfgBase); HRESULT hr; Assert (this); Assert (pBindPath); hr = DisabledBindings.HrAddBindPath ( pBindPath, INS_IGNORE_IF_DUP | INS_APPEND); DbgVerifyBindingSet (&DisabledBindings); TraceHr (ttidError, FAL, hr, FALSE, "CNetConfigCore::HrDisableBindPath"); return hr; } HRESULT CNetConfigCore::HrGetComponentBindings ( IN const CComponent* pComponent, IN DWORD dwFlags /* GB_FLAGS */, OUT CBindingSet* pBindSet) { TraceFileFunc(ttidNetcfgBase); GBCONTEXT Ctx; Assert (pComponent); Assert (pBindSet); DbgVerifyData(); // Initialize the output parameter. // if (!(dwFlags & GBF_ADD_TO_BINDSET)) { pBindSet->Clear(); } // Initialize the members of our context structure for recursion. // We set the append bindpath flags to assert if a duplicate is inserted // because we know that GetBindingsBelowComponent will not insert // duplicates under normal conditions. // ZeroMemory (&Ctx, sizeof(Ctx)); Ctx.pCore = this; Ctx.pBindSet = pBindSet; Ctx.pSourceComponent = pComponent; Ctx.fPruneDisabledBindings = (dwFlags & GBF_PRUNE_DISABLED_BINDINGS); Ctx.dwAddBindPathFlags = (dwFlags & GBF_ADD_TO_BINDSET) ? INS_IGNORE_IF_DUP : INS_ASSERT_IF_DUP; GetBindingsBelowComponent (pComponent, &Ctx); // Verify the bindset we are about to return is valid. // (Checked builds only.) // if (S_OK == Ctx.hr) { DbgVerifyBindingSet (pBindSet); } TraceHr (ttidError, FAL, Ctx.hr, FALSE, "CNetConfigCore::HrGetComponentBindings"); return Ctx.hr; } HRESULT CNetConfigCore::HrGetComponentUpperBindings ( IN const CComponent* pComponent, IN DWORD dwFlags, OUT CBindingSet* pBindSet) { TraceFileFunc(ttidNetcfgBase); HRESULT hr = S_OK; CBindPath BindPath; const CStackEntry* pScan; DWORD dwAddBindPathFlags; Assert (pComponent); Assert (FIsEnumerated(pComponent->Class())); Assert ((GBF_DEFAULT == dwFlags) || (dwFlags & GBF_ADD_TO_BINDSET) || (dwFlags & GBF_PRUNE_DISABLED_BINDINGS)); Assert (pBindSet); DbgVerifyData(); dwAddBindPathFlags = INS_APPEND | INS_IGNORE_IF_DUP; // Initialize the output parameter. // if (!(dwFlags & GBF_ADD_TO_BINDSET)) { pBindSet->Clear(); dwAddBindPathFlags = INS_APPEND | INS_ASSERT_IF_DUP; } hr = BindPath.HrReserveRoomForComponents (2); if (S_OK == hr) { // This won't fail because we've reserved enough room above. // hr = BindPath.HrInsertComponent (pComponent); Assert (S_OK == hr); Assert (1 == BindPath.CountComponents()); // For all rows in the stack table where the lower component // is the one passed in... // for (pScan = StackTable.begin(); pScan != StackTable.end(); pScan++) { if (pComponent != pScan->pLower) { continue; } // Continue if this length-2 bindpath is disabled. // if (dwFlags & GBF_PRUNE_DISABLED_BINDINGS) { if (FIsLength2BindPathDisabled (pScan->pUpper, pScan->pLower)) { continue; } } // This won't fail because we've reserved enough room above. // hr = BindPath.HrInsertComponent (pScan->pUpper); Assert (S_OK == hr); Assert (2 == BindPath.CountComponents()); hr = pBindSet->HrAddBindPath (&BindPath, dwAddBindPathFlags); if (S_OK != hr) { break; } BindPath.RemoveFirstComponent(); } // Verify the bindset we are about to return is valid. // (Checked builds only.) // DbgVerifyBindingSet (pBindSet); } TraceHr (ttidError, FAL, hr, FALSE, "CNetConfigCore::HrGetComponentUpperBindings"); return hr; } HRESULT CNetConfigCore::HrGetBindingsInvolvingComponent ( IN const CComponent* pComponent, IN DWORD dwFlags, IN OUT CBindingSet* pBindSet) { TraceFileFunc(ttidNetcfgBase); GCCONTEXT Ctx; CComponentList ComponentsAbove; UINT cExistingBindPaths; Assert (pComponent); Assert ((GBF_DEFAULT == dwFlags) || (dwFlags & GBF_ADD_TO_BINDSET) || (dwFlags & GBF_PRUNE_DISABLED_BINDINGS) || (dwFlags & GBF_ONLY_WHICH_CONTAIN_COMPONENT)); Assert (pBindSet); DbgVerifyData(); // Initialize the output parameter. // if (!(dwFlags & GBF_ADD_TO_BINDSET)) { pBindSet->Clear(); } // Since we may be adding to the bindset, be sure to exclude the existing // bindpaths from our scans below. // cExistingBindPaths = pBindSet->CountBindPaths(); // Initialize the members of our context structure for recursion. // ZeroMemory (&Ctx, sizeof(Ctx)); Ctx.pStackTable = &StackTable; Ctx.pComponents = &ComponentsAbove; Ctx.fIgnoreDontExposeLower = TRUE; GetComponentsAboveComponent (pComponent, &Ctx); if (S_OK == Ctx.hr) { // First get the bindings below the component. // Ctx.hr = HrGetComponentBindings ( pComponent, dwFlags | GBF_ADD_TO_BINDSET, pBindSet); if (S_OK == Ctx.hr) { CComponentList::const_iterator iter; const CComponent* pComponentAbove; // Now get the bindings below each of the components // above the component and add them to the bindset. // for (iter = ComponentsAbove.begin(); iter != ComponentsAbove.end(); iter++) { pComponentAbove = *iter; Assert (pComponentAbove); Ctx.hr = HrGetComponentBindings ( pComponentAbove, dwFlags | GBF_ADD_TO_BINDSET, pBindSet); if (S_OK != Ctx.hr) { break; } // Verify the bindset we are about to return is valid. // (Checked builds only.) // DbgVerifyBindingSet (pBindSet); } } // Now remove any bindings that don't involve the component. // If the component is enumerated, leave bindings that end in // NCF_DONTEXPOSELOWER components, because they are indirectly // involved in the bindings associated with the adapter. // if (S_OK == Ctx.hr) { CBindPath* pScan; BOOL fIsEnumerated; fIsEnumerated = FIsEnumerated(pComponent->Class()); if (fIsEnumerated || !(dwFlags & GBF_ONLY_WHICH_CONTAIN_COMPONENT)) { // fDelBoundToComponent means "is there a NCF_DONTEXPOSELOWER // component bound to pComponent". If there is not, we will // be setting GBF_ONLY_WHICH_CONTAIN_COMPONENT to force the // removal of any NCF_DONTEXPOSELOWER components from // the bind set below. // BOOL fDelBoundToComponent = FALSE; for (pScan = pBindSet->PGetBindPathAtIndex (cExistingBindPaths); pScan != pBindSet->end(); pScan++) { if ((pScan->PLastComponent() == pComponent) && (pScan->POwner()->m_dwCharacter & NCF_DONTEXPOSELOWER)) { fDelBoundToComponent = TRUE; break; } } if (!fDelBoundToComponent) { dwFlags |= GBF_ONLY_WHICH_CONTAIN_COMPONENT; } } pScan = pBindSet->PGetBindPathAtIndex (cExistingBindPaths); while (pScan != pBindSet->end()) { if (pScan->FContainsComponent (pComponent)) { pScan++; continue; } // At this point, we know that the bindpath does not // contain pComponent. See if we should erase it or keep it. // // If the component is not an adpater or if the caller wants // only bindpaths which contain the adapter, erase it. // if (!fIsEnumerated || (dwFlags & GBF_ONLY_WHICH_CONTAIN_COMPONENT)) { pBindSet->erase (pScan); continue; } // Otherwise, (pComponent is an adpater and the caller wants // indirect bindings involved with that adpater) we'll // erase the bindpath only if the last component is not an // NCF_DONTEXPOSELOWER component. We need to keep these // bindpaths so that the LAN UI (which shows components // involved in bindpaths that involve an adapter) can // display these NCF_DONTEXPOSELOWER components too. // else { CComponent* pLast = pScan->PLastComponent(); if (!(pLast->m_dwCharacter & NCF_DONTEXPOSELOWER)) { pBindSet->erase (pScan); continue; } } pScan++; } } } TraceHr (ttidError, FAL, Ctx.hr, FALSE, "CNetConfigCore::HrGetBindingsInvolvingComponent"); return Ctx.hr; } HRESULT CNetConfigCore::HrGetFiltersEnabledForAdapter ( IN const CComponent* pAdapter, OUT CComponentList* pFilters) { TraceFileFunc(ttidNetcfgBase); HRESULT hr; const CStackEntry* pScan; Assert (this); Assert (pAdapter); Assert (FIsEnumerated(pAdapter->Class())); Assert (pFilters); // Initialize the output parameter. // pFilters->Clear(); // Scan the stack table looking for lower component matches to pAdapter. // When found, and if the upper component is a filter, and if the // direct bindpath is not disabled, add the filter to the output list. // for (pScan = StackTable.begin(); pScan != StackTable.end(); pScan++) { // Looking for lower component matches to the adapter where // the upper component is a filter. Ignore all others. // if ((pScan->pLower != pAdapter) || !pScan->pUpper->FIsFilter()) { continue; } Assert (pScan->pUpper->FIsFilter()); Assert (pScan->pLower == pAdapter); // If the bindpath is not disabled, add the filter to the list. // if (!FIsLength2BindPathDisabled (pScan->pUpper, pScan->pLower)) { // Assert if a duplicate because the same filter can't be bound // to the same adapter more than once. // No need to sort when we insert because the class of all of // the components in this list will be the same. // hr = pFilters->HrInsertComponent (pScan->pUpper, INS_ASSERT_IF_DUP | INS_NON_SORTED); if (S_OK != hr) { return hr; } } } return S_OK; } // (Takes ownership of pComponent) // HRESULT CNetConfigCore::HrAddComponentToCore ( IN CComponent* pComponent, IN DWORD dwFlags /* INS_FLAGS */) { TraceFileFunc(ttidNetcfgBase); HRESULT hr; Assert (pComponent); Assert ((INS_SORTED == dwFlags) || (INS_NON_SORTED == dwFlags)); DbgVerifyExternalDataLoadedForAllComponents (); pComponent->Ext.DbgVerifyExternalDataLoaded (); // Make sure we are not trying to insert a component with a PnpId that // is the same as one already in the core. Note that we do not perform // this check inside of CComponentList::HrInsertComponent because it is // not appropriate for all component lists. Specifically, the dirty // component list may end up having two different components with the // same PnpId for the case when we are asked to install an adapter that // is pending removal. In this case the new adapter will get the same // PnpId as the one that was removed. We'll remove the old one from // core to insert the new one so the dirty component list will have // both components with the same PnpId. // if (FIsEnumerated(pComponent->Class()) && Components.PFindComponentByPnpId (pComponent->m_pszPnpId)) { AssertSz (FALSE, "Asked to add a component with a duplicate PnpId!"); return E_INVALIDARG; } // Insert the component into the list. This should only fail if we // are out of memory. // hr = Components.HrInsertComponent ( pComponent, dwFlags | INS_ASSERT_IF_DUP); if (S_OK == hr) { // Insert the appropriate stack entries for pComponent based on // how it binds with the other components. // hr = StackTable.HrInsertStackEntriesForComponent ( pComponent, &Components, dwFlags); // If we failed to insert the stack entries, undo the insertion // into the component list. // if (S_OK != hr) { Components.RemoveComponent (pComponent); } } // Error or not, we should still have a valid core. // DbgVerifyData(); TraceHr (ttidError, FAL, hr, FALSE, "CNetConfigCore::HrAddComponentToCore"); return hr; } VOID CNetConfigCore::RemoveComponentFromCore ( IN const CComponent* pComponent) { TraceFileFunc(ttidNetcfgBase); Assert (this); Assert (pComponent); Components.RemoveComponent (pComponent); StackTable.RemoveEntriesWithComponent (pComponent); DisabledBindings.RemoveBindPathsWithComponent (pComponent); #if DBG // We set m_fRemovedAComponent so that when subsequent // DbgVerifyBindingSet calls are made, we don't assert on components // that are in the binding set but not in the core. This is a natural // occurance for the m_DeletedBindings we build up during removal of // components. // m_fRemovedAComponent = TRUE; // Note: need to find a convenient time during Apply where // we set this flag back to FALSE. #endif } // static HRESULT CNetConfig::HrCreateInstance ( IN class CImplINetCfg* pINetCfg, OUT CNetConfig** ppNetConfig) { TraceFileFunc(ttidNetcfgBase); Assert (pINetCfg); HRESULT hr = E_OUTOFMEMORY; CNetConfig* pObj; pObj = new CNetConfig; if (pObj) { // Load the configuration binary. If we have have the write lock, // request write access, otherwise, request read access. // Requesting write access means this will only succeed if the // volatile key indicating we need a reboot does not exist. // hr = HrLoadNetworkConfigurationFromRegistry ( (pINetCfg->m_WriteLock.FIsOwnedByMe ()) ? KEY_WRITE : KEY_READ, pObj); if (S_OK == hr) { pObj->Core.DbgVerifyData(); pObj->Notify.HoldINetCfg (pINetCfg); *ppNetConfig = pObj; } else { delete pObj; } } TraceHr (ttidError, FAL, hr, FALSE, "CNetConfig::HrCreateInstance"); return hr; } CNetConfig::~CNetConfig () { TraceFileFunc(ttidNetcfgBase); Core.Free (); } HRESULT CNetConfig::HrEnsureExternalDataLoadedForAllComponents () { TraceFileFunc(ttidNetcfgBase); HRESULT hr; HRESULT hrRet; CComponentList::iterator iter; CComponent* pComponent; hrRet = S_OK; // This is a while loop instead of a for loop because of the // potential for conditional advancement of the iterator. If we // remove a component from the core while looping, we don't increment // the iterator for the next iteration. // iter = Core.Components.begin(); while (iter != Core.Components.end()) { pComponent = *iter; Assert (pComponent); hr = pComponent->Ext.HrEnsureExternalDataLoaded (); if ((SPAPI_E_NO_SUCH_DEVINST == hr) || (SPAPI_E_KEY_DOES_NOT_EXIST == hr)) { g_pDiagCtx->Printf (ttidBeDiag, "Removing %s from the core because its external data is missing\n", pComponent->PszGetPnpIdOrInfId()); Core.RemoveComponentFromCore (pComponent); delete pComponent; hr = S_OK; // Because we removed this component from the core, the // list has been adjusted and the next component is now this // iter. Therefore, just continue without incrementing iter. // continue; } // Remember the first error as our return code, but keep going. // Like the name says, we are to load data for all components so // don't stop just because one fails. // if ((S_OK != hr) && (S_OK == hrRet)) { hrRet = hr; } iter++; } TraceHr (ttidError, FAL, hrRet, FALSE, "CNetConfig::HrEnsureExternalDataLoadedForAllComponents"); return hrRet; } #if DBG VOID CNetConfigCore::DbgVerifyData () const { TraceFileFunc(ttidNetcfgBase); HRESULT hr; CComponentList::const_iterator iter; const CComponent* pComponent; const CStackEntry* pStackEntry; const CComponent* pUpper; const CComponent* pLower; CHAR szBuffer [512]; for (pStackEntry = StackTable.begin(); pStackEntry != StackTable.end(); pStackEntry++) { pUpper = pStackEntry->pUpper; pLower = pStackEntry->pLower; Assert (pUpper); Assert (pLower); Assert (pUpper != pLower); // Can't access upper and lower range if the external data isn't // loaded. // if (pUpper->Ext.DbgIsExternalDataLoaded() && pLower->Ext.DbgIsExternalDataLoaded()) { if (!pUpper->FCanDirectlyBindTo (pLower, NULL, NULL)) { wsprintfA (szBuffer, "%S should not bind to %S. They are in the " "stack table as if they do. (Likely upgrade problem.)", pUpper->PszGetPnpIdOrInfId(), pLower->PszGetPnpIdOrInfId()); AssertSzFn (szBuffer, FAL); } } if (!FIsEnumerated (pUpper->Class())) { if (pUpper != Components.PFindComponentByInfId ( pUpper->m_pszInfId, NULL)) { wsprintfA (szBuffer, "%S is an upper component in the stack " "table, but was not found in the component list.", pUpper->m_pszInfId); AssertSzFn (szBuffer, FAL); } } else { if (pUpper != Components.PFindComponentByPnpId ( pUpper->m_pszPnpId)) { wsprintfA (szBuffer, "%S is an upper component in the stack " "table, but was not found in the component list.", pUpper->m_pszInfId); AssertSzFn (szBuffer, FAL); } } if (!FIsEnumerated (pLower->Class())) { if (pLower != Components.PFindComponentByInfId ( pLower->m_pszInfId, NULL)) { wsprintfA (szBuffer, "%S is a lower component in the stack " "table, but was not found in the component list.", pLower->m_pszInfId); AssertSzFn (szBuffer, FAL); } } else { if (pLower != Components.PFindComponentByPnpId ( pLower->m_pszPnpId)) { wsprintfA (szBuffer, "%S is a lower component in the stack " "table, but was not found in the component list.", pLower->m_pszInfId); AssertSzFn (szBuffer, FAL); } } if (pUpper != Components.PFindComponentByInstanceGuid (&pUpper->m_InstanceGuid)) { wsprintfA (szBuffer, "%S is an upper component in the stack " "table, but was not found in the component list by GUID.", pUpper->PszGetPnpIdOrInfId()); AssertSzFn (szBuffer, FAL); } if (pLower != Components.PFindComponentByInstanceGuid (&pLower->m_InstanceGuid)) { wsprintfA (szBuffer, "%S is a lower component in the stack " "table, but was not found in the component list by GUID.", pLower->PszGetPnpIdOrInfId()); AssertSzFn (szBuffer, FAL); } } } VOID CNetConfigCore::DbgVerifyExternalDataLoadedForAllComponents () const { TraceFileFunc(ttidNetcfgBase); CComponentList::const_iterator iter; const CComponent* pComponent; for (iter = Components.begin(); iter != Components.end(); iter++) { pComponent = *iter; Assert (pComponent); pComponent->Ext.DbgVerifyExternalDataLoaded(); } } VOID CNetConfigCore::DbgVerifyBindingSet ( const CBindingSet* pBindSet) const { TraceFileFunc(ttidNetcfgBase); const CBindPath* pPath; const CBindPath* pOtherPath; CBindPath::const_iterator iter; const CComponent* pComponent; Assert (pBindSet); // First make sure every component in the set is in our component // list. We only do this if we're not in the state where a component // has been removed from the core. For this case, its okay if those // components still exist in binding sets. // if (!m_fRemovedAComponent) { for (pPath = pBindSet->begin(); pPath != pBindSet->end(); pPath++) { for (iter = pPath->begin(); iter != pPath->end(); iter++) { pComponent = *iter; Assert (Components.FComponentInList (pComponent)); } } } // Make sure there are no duplicate bindpaths in the set. // for (pPath = pBindSet->begin(); pPath != pBindSet->end(); pPath++) { Assert (!pPath->FIsEmpty()); for (pOtherPath = pBindSet->begin(); pOtherPath != pBindSet->end(); pOtherPath++) { if (pPath == pOtherPath) { continue; } Assert (!pPath->FIsSameBindPathAs (pOtherPath)); } } } #endif // DBG