Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix S3881: Rule throws AD0001 with SyntaxTree not part of the compilation #1804

Closed
jcurl opened this issue Aug 28, 2018 · 10 comments
Closed
Assignees
Labels
Type: Bug Exceptions and blocking issues during analysis.
Milestone

Comments

@jcurl
Copy link

jcurl commented Aug 28, 2018

Description

AD0001 internal error

Repro steps

Severity	Code	Description	Project	File	Line	Suppression State	Tool
Warning	AD0001	Analyzer 'SonarAnalyzer.Rules.CSharp.ImplementIDisposableCorrectly' threw an exception of type 'System.ArgumentException' with message 'SyntaxTree 'namespace RJCP.RjBuild.Infrastructure.Collections.Generic
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// A collection of items which are named and then indexed in a dictionary by name.
    /// </summary>
    /// <typeparam name="T">The type of the object which this collection manages.</typeparam>
    /// <remarks>
    /// By having a base class implementation for managing the collection, we present a consistent
    /// interface to the user application.
    /// </remarks>
    public abstract class NamedItemCollection<T> : IDisposable, ICollection<T> where T : class, INamedItem
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NamedItemCollection{T}"/> class without a <see cref="Name"/>.
        /// </summary>
        /// <remarks>
        /// This constructor is intended for collections that need to be able to set the name during the constructor,
        /// instead of prior (for example, a file might need to be parsed to determine the name). The name should be
        /// set once the construction of your object is complete.
        /// </remarks>
        protected NamedItemCollection() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="NamedItemCollection{T}"/> class.
        /// </summary>
        /// <param name="name">The name of the objects that this instance manages.</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> may not be <see langword="null"/>.</exception>
        protected NamedItemCollection(string name)
        {
            if (name == null) throw new ArgumentNullException(nameof(name));
            Name = name;
        }

        private string m_Name;

        /// <summary>
        /// Gets the name of this collection.
        /// </summary>
        /// <value>
        /// The name of this collection.
        /// </value>
        public string Name
        {
            get
            {
                if (m_Name == null) {
                    return GetType().ToString();
                }

                return m_Name;
            }
            protected set
            {
                if (value == null) throw new ArgumentNullException(nameof(value));
                if (m_Name != null) throw new InvalidOperationException(Resources.Infra_Collections_NamedItem_NameLocked);
                m_Name = value;
            }
        }

        private Dictionary<string, T> m_Items = new Dictionary<string, T>();

        /// <summary>
        /// Gets the item of the specified device name with <paramref name="key"/>.
        /// </summary>
        /// <value>
        /// The requested item. If the item cannot be found, <see langword="null"/> can be returned.
        /// </value>
        /// <param name="key">The item name.</param>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="key"/> may not be <see langword="null"/>.</exception>
        public T this[string key]
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException(Name);
                if (key == null) throw new ArgumentNullException(nameof(key));

                T item;
                if (m_Items.TryGetValue(key, out item)) return item;

                string message = string.Format(Resources.Infra_Collections_NamedItem_NotFound, key, Name);
                throw new ArgumentException(message);
            }
        }

        /// <summary>
        /// Attempts to get the item for the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="item">The item.</param>
        /// <returns><see langword="true"/> if the item with the <paramref name="key"/> exists in the collection and so
        /// is returned, <see langword="false"/> if the <paramref name="key"/> doesn't exist and so <paramref name="item"/>
        /// is undefined.</returns>
        /// <exception cref="System.ObjectDisposedException"></exception>
        /// <exception cref="System.ArgumentNullException">key</exception>
        public bool TryGetValue(string key, out T item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (key == null) throw new ArgumentNullException(nameof(key));
            return (m_Items.TryGetValue(key, out item));
        }

        #region IEnumerable<T>
        /// <summary>
        /// Returns an enumerator that iterates through the collection of items.
        /// </summary>
        /// <returns>
        /// An enumerator that can be used to iterate through the collection of items.
        /// </returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        public IEnumerator<T> GetEnumerator()
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            return m_Items.Values.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion

        #region ICollection<T>
        /// <summary>
        /// Adds an item to this collection.
        /// </summary>
        /// <param name="item">The item to add to the collection.</param>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ArgumentNullException"><paramref name="item"/> may not be <see langword="null"/>.</exception>
        /// <exception cref="System.ArgumentException">Invalid <paramref name="item"/> name;
        /// or an <paramref name="item"/> with same name already exists in the collection.</exception>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <remarks>
        /// It is expected that the name of the item does not change after being added. Doing so may result
        /// in unexpected behavior.
        /// </remarks>
        public void Add(T item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);

            OnAdd(item);
            InternalAdd(item);
            try {
                OnAddComplete(item);
            } catch {
                m_Items.Remove(item.Name);
                throw;
            }
        }

        /// <summary>
        /// Adds a range of items to the collection.
        /// </summary>
        /// <param name="items">The items that should be added.</param>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ArgumentNullException">An element in <paramref name="items"/> may not be <see langword="null"/>.</exception>
        /// <exception cref="System.ArgumentException">An invalid element name in <paramref name="items"/>;
        /// or an element in <paramref name="items"/> with same name already exists in the collection.</exception>
        public void AddRange(IEnumerable<T> items)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);

            foreach (T item in items) {
                OnAdd(item);
                InternalAddCheck(item);
            }
            foreach (T item in items) {
                m_Items.Add(item.Name, item);
                try {
                    OnAddComplete(item);
                } catch {
                    m_Items.Remove(item.Name);
                    throw;
                }
            }
        }

        private void InternalAddCheck(T item)
        {
            if (IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);
            if (item == null) throw new ArgumentNullException(nameof(item));
            if (string.IsNullOrWhiteSpace(item.Name)) throw new ArgumentException(Resources.Infra_Collections_NamedItem_InvalidName, nameof(item));
            if (m_Items.ContainsKey(item.Name)) {
                string message = string.Format(Resources.Infra_Collections_NamedItem_Duplicate, item.Name);
                throw new ArgumentException(message);
            }
        }

        /// <summary>
        /// Adds the item to the collection directly without raising events.
        /// </summary>
        /// <param name="item">The item that should be added.</param>
        protected void InternalAdd(T item)
        {
            InternalAddCheck(item);
            m_Items.Add(item.Name, item);
        }

        /// <summary>
        /// A derived class can override this method to perform additional checks on the item before it is added.
        /// </summary>
        /// <param name="item">The item to add.</param>
        /// <remarks>
        /// Implementor should override this method and raise an exception to prevent the item from being added.
        /// </remarks>
        protected virtual void OnAdd(T item) { }

        /// <summary>
        /// Called after the item is added tot he collection.
        /// </summary>
        /// <param name="item">The item that was added to the collection.</param>
        protected virtual void OnAddComplete(T item) { }

        /// <summary>
        /// Removes all items from this collection.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <remarks>
        /// Removing all items from this collection doesn't dispose the items in the collection. You should either
        /// <see cref="Dispose()"/> this collection or dispose the objects individually if required.
        /// </remarks>
        public void Clear()
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);

            OnClear();
            InternalClear();
            OnClearComplete();
        }

        /// <summary>
        /// Clears the internal collection without raising events.
        /// </summary>
        protected void InternalClear()
        {
            m_Items.Clear();
        }

        /// <summary>
        /// Called before the collection is about to be cleared.
        /// </summary>
        /// <remarks>
        /// If there is an error that should prevent the collection being cleared, an exception should be raised here.
        /// </remarks>
        protected virtual void OnClear() { }

        /// <summary>
        /// Called after the internal collection is cleared.
        /// </summary>
        protected virtual void OnClearComplete() { }

        /// <summary>
        /// Determines whether the collection contains an item of the specified name.
        /// </summary>
        /// <param name="item">The item name to check for.</param>
        /// <returns><see langword="true"/> if the collection contains an item with the name specified.</returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <remarks>
        /// This method returns <see langword="false"/> if no element with the keyword exists. If <paramref name="item"/>
        /// is <see langword="null"/>, then <see langword="false"/> is returned.
        /// </remarks>
        public bool Contains(string item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (item == null) return false;
            return m_Items.ContainsKey(item);
        }

        /// <summary>
        /// Determines whether the collection contains an item of the same name.
        /// </summary>
        /// <param name="item">The item whose name to locate in the collection.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="item" /> is found in the collection; otherwise, <see langword="false"/>.
        /// </returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="item"/> may not be <see langword="null"/>.</exception>
        public bool Contains(T item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (item == null) throw new ArgumentNullException(nameof(item));
            return m_Items.ContainsValue(item);
        }

        /// <summary>
        /// Copies the collection of items in the collection to the specified array.
        /// </summary>
        /// <param name="array">The array to copy the collection to.</param>
        /// <param name="arrayIndex">Index in the array where to start copying to.</param>
        /// <exception cref="ArgumentNullException"><paramref name="array"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="array"/> is multidimensional; or the number of elements in
        /// the collection is greater than the available space from <paramref name="arrayIndex"/> to the
        /// end of the destination <paramref name="array"/>; or the collection cannot be automatically
        /// cast to the type of the destination <paramref name="array"/>.</exception>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        public void CopyTo(T[] array, int arrayIndex)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            m_Items.Values.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Gets the number of elements contained in the collection.
        /// </summary>
        /// <returns>The number of elements contained in the collection.</returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        public int Count
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException(Name);
                return m_Items.Count;
            }
        }

        private bool m_IsReadOnly;

        /// <summary>
        /// Gets a value indicating whether the collection is read-only.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Collection is read only and cannot change this property.</exception>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <returns><see langword="true"/> if the collection is read-only; otherwise, <see langword="false"/>.</returns>
        /// <remarks>
        /// Initially, the collection is read/write. You may write to this property to set the collection to read-only,
        /// which the collection can never be made read/write again.
        /// </remarks>
        public bool IsReadOnly
        {
            get { return m_IsReadOnly; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException(Name);
                if (m_IsReadOnly != value) {
                    if (m_IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);
                    m_IsReadOnly = value;
                }
            }
        }

        /// <summary>
        /// Removes the occurrence of the item from the collection.
        /// </summary>
        /// <param name="item">The item to remove from the collection.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="item" /> was successfully removed from the collection;
        /// otherwise, <see langword="false"/>. This method also returns <see langword="false"/> if <paramref name="item" /> is not found
        /// in the collection.
        /// </returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ArgumentNullException"><paramref name="item"/> may not be <see langword="null"/>.</exception>
        /// <remarks>
        /// Removing an item from the collection doesn't dispose the item being removed from the collection.
        /// </remarks>
        public bool Remove(T item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (item == null) throw new ArgumentNullException(nameof(item));
            if (IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);
            if (!Contains(item)) return false;
            return InternalRemoveWithChecks(item.Name, item);
        }

        /// <summary>
        /// Removes the occurrence of the item having the specified name from the collection.
        /// </summary>
        /// <param name="item">The item name to remove from the collection.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="item" /> was successfully removed from the collection;
        /// otherwise, <see langword="false"/>. This method also returns <see langword="false"/> if <paramref name="item" /> is not found
        /// in the collection.
        /// </returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ArgumentNullException"><paramref name="item"/> may not be <see langword="null"/>.</exception>
        public bool Remove(string item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (item == null) throw new ArgumentNullException(nameof(item));
            if (IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);
            if (!Contains(item)) return false;
            return InternalRemoveWithChecks(item, null);
        }

        private bool InternalRemoveWithChecks(string itemName, T item)
        {
            if (item == null) item = m_Items[itemName];

            OnRemove(item);
            if (InternalRemove(itemName)) {
                try {
                    OnRemoveComplete(item);
                    return true;
                } catch {
                    m_Items.Add(itemName, item);
                    throw;
                }
            }
            return false;
        }

        /// <summary>
        /// Internally remove the item from the collection without raising events.
        /// </summary>
        /// <param name="item">The item identifier that should be removed.</param>
        /// <returns><see langword="true"/> if the item was removed, <see langword="false"/> otherwise.</returns>
        protected bool InternalRemove(string itemName)
        {
            if (itemName == null) throw new ArgumentNullException(nameof(itemName));
            return m_Items.Remove(itemName);
        }

        /// <summary>
        /// Called when before an item is removed from the collection.
        /// </summary>
        /// <param name="item">The item to be removed.</param>
        protected virtual void OnRemove(T item) { }

        /// <summary>
        /// Called when the item has been removed from the collection.
        /// </summary>
        /// <param name="item">The item that was removed.</param>
        /// <remarks>
        /// This method is only called if the object was found in the collection and was removed.
        /// </remarks>
        protected virtual void OnRemoveComplete(T item) { }
        #endregion

        #region IDisposable
        /// <summary>
        /// Gets a value indicating whether this instance is disposed.
        /// </summary>
        /// <value>
        /// <see langword="true"/> if this instance is disposed; otherwise, <see langword="false"/>.
        /// </value>
        protected bool IsDisposed
        {
            get { return m_Items == null; }
        }

        /// <summary>
        /// Dispose all devices in this collection.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose all devices in this collection.
        /// </summary>
        /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
        /// <see langword="false"/> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!IsDisposed && disposing) {
                foreach (T item in m_Items.Values) {
                    IDisposable disposableItem = item as IDisposable;
                    if (disposableItem != null) disposableItem.Dispose();
                }
                m_Items = null;
            }
        }
        #endregion
    }
}' not found to remove
Parameter name: syntaxTree'.	RjBuildTest		1	Active	SonarAnalyzer.CSharp

Expected behavior

No error AD0001

Actual behavior

See above

Known workarounds

Ignore, at the peril of other errors not being tested

Related information

  • SonarC# Version VS Extension 4.3.0.3718
  • Visual Studio Version 2017 Enterprise
@Evangelink
Copy link
Contributor

Hi @jcurl,

Could you let us know the version of SonarC# you are using? Copy-pasting your class and using the latest NuGet, I cannot reproduce the issue.

@Evangelink Evangelink self-assigned this Aug 29, 2018
@Evangelink Evangelink added this to the Support milestone Aug 29, 2018
@jcurl
Copy link
Author

jcurl commented Aug 29, 2018

Hello, the version as given by the Visual Studio extensions manager is in the problem description as SonarC# Version VS Extension 4.3.0.3718

@jcurl
Copy link
Author

jcurl commented Sep 1, 2018

If it helps, the target .NET framework is 4.5, and so the NuGet package I'm using is the latest for .NET 4.5 (I can't use the newest as they target .NET 4.6, etc.)

@jcurl
Copy link
Author

jcurl commented Sep 9, 2018

On another computer where SonarLint is C# 4.2.0.3692, the AD001 doesn't appear to occur. I upgraded to 4.4.0.x and the error is still there.

Severity	Code	Description	Project	File	Line	Suppression State
Warning	AD0001	Analyzer 'SonarAnalyzer.Rules.CSharp.ImplementIDisposableCorrectly' threw an exception of type 'System.ArgumentException' with message 'SyntaxTree 'namespace RJCP.RjBuild.Infrastructure.Collections.Generic
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// A collection of items which are named and then indexed in a dictionary by name.
    /// </summary>
    /// <typeparam name="T">The type of the object which this collection manages.</typeparam>
    /// <remarks>
    /// By having a base class implementation for managing the collection, we present a consistent
    /// interface to the user application.
    /// </remarks>
    public abstract class NamedItemCollection<T> : IDisposable, ICollection<T> where T : class, INamedItem
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NamedItemCollection{T}"/> class without a <see cref="Name"/>.
        /// </summary>
        /// <remarks>
        /// This constructor is intended for collections that need to be able to set the name during the constructor,
        /// instead of prior (for example, a file might need to be parsed to determine the name). The name should be
        /// set once the construction of your object is complete.
        /// </remarks>
        protected NamedItemCollection() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="NamedItemCollection{T}"/> class.
        /// </summary>
        /// <param name="name">The name of the objects that this instance manages.</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> may not be <see langword="null"/>.</exception>
        protected NamedItemCollection(string name)
        {
            if (name == null) throw new ArgumentNullException(nameof(name));
            Name = name;
        }

        private string m_Name;

        /// <summary>
        /// Gets the name of this collection.
        /// </summary>
        /// <value>
        /// The name of this collection.
        /// </value>
        public string Name
        {
            get
            {
                if (m_Name == null) {
                    return GetType().ToString();
                }

                return m_Name;
            }
            protected set
            {
                if (value == null) throw new ArgumentNullException(nameof(value));
                if (m_Name != null) throw new InvalidOperationException(Resources.Infra_Collections_NamedItem_NameLocked);
                m_Name = value;
            }
        }

        private Dictionary<string, T> m_Items = new Dictionary<string, T>();

        /// <summary>
        /// Gets the item of the specified device name with <paramref name="key"/>.
        /// </summary>
        /// <value>
        /// The requested item. If the item cannot be found, <see langword="null"/> can be returned.
        /// </value>
        /// <param name="key">The item name.</param>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="key"/> may not be <see langword="null"/>.</exception>
        public T this[string key]
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException(Name);
                if (key == null) throw new ArgumentNullException(nameof(key));

                T item;
                if (m_Items.TryGetValue(key, out item)) return item;

                string message = string.Format(Resources.Infra_Collections_NamedItem_NotFound, key, Name);
                throw new ArgumentException(message);
            }
        }

        /// <summary>
        /// Attempts to get the item for the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="item">The item.</param>
        /// <returns><see langword="true"/> if the item with the <paramref name="key"/> exists in the collection and so
        /// is returned, <see langword="false"/> if the <paramref name="key"/> doesn't exist and so <paramref name="item"/>
        /// is undefined.</returns>
        /// <exception cref="System.ObjectDisposedException"></exception>
        /// <exception cref="System.ArgumentNullException">key</exception>
        public bool TryGetValue(string key, out T item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (key == null) throw new ArgumentNullException(nameof(key));
            return (m_Items.TryGetValue(key, out item));
        }

        #region IEnumerable<T>
        /// <summary>
        /// Returns an enumerator that iterates through the collection of items.
        /// </summary>
        /// <returns>
        /// An enumerator that can be used to iterate through the collection of items.
        /// </returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        public IEnumerator<T> GetEnumerator()
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            return m_Items.Values.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion

        #region ICollection<T>
        /// <summary>
        /// Adds an item to this collection.
        /// </summary>
        /// <param name="item">The item to add to the collection.</param>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ArgumentNullException"><paramref name="item"/> may not be <see langword="null"/>.</exception>
        /// <exception cref="System.ArgumentException">Invalid <paramref name="item"/> name;
        /// or an <paramref name="item"/> with same name already exists in the collection.</exception>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <remarks>
        /// It is expected that the name of the item does not change after being added. Doing so may result
        /// in unexpected behavior.
        /// </remarks>
        public void Add(T item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);

            OnAdd(item);
            InternalAdd(item);
            try {
                OnAddComplete(item);
            } catch {
                m_Items.Remove(item.Name);
                throw;
            }
        }

        /// <summary>
        /// Adds a range of items to the collection.
        /// </summary>
        /// <param name="items">The items that should be added.</param>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ArgumentNullException">An element in <paramref name="items"/> may not be <see langword="null"/>.</exception>
        /// <exception cref="System.ArgumentException">An invalid element name in <paramref name="items"/>;
        /// or an element in <paramref name="items"/> with same name already exists in the collection.</exception>
        public void AddRange(IEnumerable<T> items)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);

            foreach (T item in items) {
                OnAdd(item);
                InternalAddCheck(item);
            }
            foreach (T item in items) {
                m_Items.Add(item.Name, item);
                try {
                    OnAddComplete(item);
                } catch {
                    m_Items.Remove(item.Name);
                    throw;
                }
            }
        }

        private void InternalAddCheck(T item)
        {
            if (IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);
            if (item == null) throw new ArgumentNullException(nameof(item));
            if (string.IsNullOrWhiteSpace(item.Name)) throw new ArgumentException(Resources.Infra_Collections_NamedItem_InvalidName, nameof(item));
            if (m_Items.ContainsKey(item.Name)) {
                string message = string.Format(Resources.Infra_Collections_NamedItem_Duplicate, item.Name);
                throw new ArgumentException(message);
            }
        }

        /// <summary>
        /// Adds the item to the collection directly without raising events.
        /// </summary>
        /// <param name="item">The item that should be added.</param>
        protected void InternalAdd(T item)
        {
            InternalAddCheck(item);
            m_Items.Add(item.Name, item);
        }

        /// <summary>
        /// A derived class can override this method to perform additional checks on the item before it is added.
        /// </summary>
        /// <param name="item">The item to add.</param>
        /// <remarks>
        /// Implementor should override this method and raise an exception to prevent the item from being added.
        /// </remarks>
        protected virtual void OnAdd(T item) { }

        /// <summary>
        /// Called after the item is added tot he collection.
        /// </summary>
        /// <param name="item">The item that was added to the collection.</param>
        protected virtual void OnAddComplete(T item) { }

        /// <summary>
        /// Removes all items from this collection.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <remarks>
        /// Removing all items from this collection doesn't dispose the items in the collection. You should either
        /// <see cref="Dispose()"/> this collection or dispose the objects individually if required.
        /// </remarks>
        public void Clear()
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);

            OnClear();
            InternalClear();
            OnClearComplete();
        }

        /// <summary>
        /// Clears the internal collection without raising events.
        /// </summary>
        protected void InternalClear()
        {
            m_Items.Clear();
        }

        /// <summary>
        /// Called before the collection is about to be cleared.
        /// </summary>
        /// <remarks>
        /// If there is an error that should prevent the collection being cleared, an exception should be raised here.
        /// </remarks>
        protected virtual void OnClear() { }

        /// <summary>
        /// Called after the internal collection is cleared.
        /// </summary>
        protected virtual void OnClearComplete() { }

        /// <summary>
        /// Determines whether the collection contains an item of the specified name.
        /// </summary>
        /// <param name="item">The item name to check for.</param>
        /// <returns><see langword="true"/> if the collection contains an item with the name specified.</returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <remarks>
        /// This method returns <see langword="false"/> if no element with the keyword exists. If <paramref name="item"/>
        /// is <see langword="null"/>, then <see langword="false"/> is returned.
        /// </remarks>
        public bool Contains(string item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (item == null) return false;
            return m_Items.ContainsKey(item);
        }

        /// <summary>
        /// Determines whether the collection contains an item of the same name.
        /// </summary>
        /// <param name="item">The item whose name to locate in the collection.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="item" /> is found in the collection; otherwise, <see langword="false"/>.
        /// </returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="item"/> may not be <see langword="null"/>.</exception>
        public bool Contains(T item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (item == null) throw new ArgumentNullException(nameof(item));
            return m_Items.ContainsValue(item);
        }

        /// <summary>
        /// Copies the collection of items in the collection to the specified array.
        /// </summary>
        /// <param name="array">The array to copy the collection to.</param>
        /// <param name="arrayIndex">Index in the array where to start copying to.</param>
        /// <exception cref="ArgumentNullException"><paramref name="array"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="array"/> is multidimensional; or the number of elements in
        /// the collection is greater than the available space from <paramref name="arrayIndex"/> to the
        /// end of the destination <paramref name="array"/>; or the collection cannot be automatically
        /// cast to the type of the destination <paramref name="array"/>.</exception>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        public void CopyTo(T[] array, int arrayIndex)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            m_Items.Values.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Gets the number of elements contained in the collection.
        /// </summary>
        /// <returns>The number of elements contained in the collection.</returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        public int Count
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException(Name);
                return m_Items.Count;
            }
        }

        private bool m_IsReadOnly;

        /// <summary>
        /// Gets a value indicating whether the collection is read-only.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Collection is read only and cannot change this property.</exception>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <returns><see langword="true"/> if the collection is read-only; otherwise, <see langword="false"/>.</returns>
        /// <remarks>
        /// Initially, the collection is read/write. You may write to this property to set the collection to read-only,
        /// which the collection can never be made read/write again.
        /// </remarks>
        public bool IsReadOnly
        {
            get { return m_IsReadOnly; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException(Name);
                if (m_IsReadOnly != value) {
                    if (m_IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);
                    m_IsReadOnly = value;
                }
            }
        }

        /// <summary>
        /// Removes the occurrence of the item from the collection.
        /// </summary>
        /// <param name="item">The item to remove from the collection.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="item" /> was successfully removed from the collection;
        /// otherwise, <see langword="false"/>. This method also returns <see langword="false"/> if <paramref name="item" /> is not found
        /// in the collection.
        /// </returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ArgumentNullException"><paramref name="item"/> may not be <see langword="null"/>.</exception>
        /// <remarks>
        /// Removing an item from the collection doesn't dispose the item being removed from the collection.
        /// </remarks>
        public bool Remove(T item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (item == null) throw new ArgumentNullException(nameof(item));
            if (IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);
            if (!Contains(item)) return false;
            return InternalRemoveWithChecks(item.Name, item);
        }

        /// <summary>
        /// Removes the occurrence of the item having the specified name from the collection.
        /// </summary>
        /// <param name="item">The item name to remove from the collection.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="item" /> was successfully removed from the collection;
        /// otherwise, <see langword="false"/>. This method also returns <see langword="false"/> if <paramref name="item" /> is not found
        /// in the collection.
        /// </returns>
        /// <exception cref="System.ObjectDisposedException">Object is already disposed.</exception>
        /// <exception cref="System.InvalidOperationException">Collection is read only.</exception>
        /// <exception cref="System.ArgumentNullException"><paramref name="item"/> may not be <see langword="null"/>.</exception>
        public bool Remove(string item)
        {
            if (IsDisposed) throw new ObjectDisposedException(Name);
            if (item == null) throw new ArgumentNullException(nameof(item));
            if (IsReadOnly) throw new InvalidOperationException(Resources.Infra_Collections_ReadOnly);
            if (!Contains(item)) return false;
            return InternalRemoveWithChecks(item, null);
        }

        private bool InternalRemoveWithChecks(string itemName, T item)
        {
            if (item == null) item = m_Items[itemName];

            OnRemove(item);
            if (InternalRemove(itemName)) {
                try {
                    OnRemoveComplete(item);
                    return true;
                } catch {
                    m_Items.Add(itemName, item);
                    throw;
                }
            }
            return false;
        }

        /// <summary>
        /// Internally remove the item from the collection without raising events.
        /// </summary>
        /// <param name="item">The item identifier that should be removed.</param>
        /// <returns><see langword="true"/> if the item was removed, <see langword="false"/> otherwise.</returns>
        protected bool InternalRemove(string itemName)
        {
            if (itemName == null) throw new ArgumentNullException(nameof(itemName));
            return m_Items.Remove(itemName);
        }

        /// <summary>
        /// Called when before an item is removed from the collection.
        /// </summary>
        /// <param name="item">The item to be removed.</param>
        protected virtual void OnRemove(T item) { }

        /// <summary>
        /// Called when the item has been removed from the collection.
        /// </summary>
        /// <param name="item">The item that was removed.</param>
        /// <remarks>
        /// This method is only called if the object was found in the collection and was removed.
        /// </remarks>
        protected virtual void OnRemoveComplete(T item) { }
        #endregion

        #region IDisposable
        /// <summary>
        /// Gets a value indicating whether this instance is disposed.
        /// </summary>
        /// <value>
        /// <see langword="true"/> if this instance is disposed; otherwise, <see langword="false"/>.
        /// </value>
        protected bool IsDisposed
        {
            get { return m_Items == null; }
        }

        /// <summary>
        /// Dispose all devices in this collection.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose all devices in this collection.
        /// </summary>
        /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
        /// <see langword="false"/> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!IsDisposed && disposing) {
                foreach (T item in m_Items.Values) {
                    IDisposable disposableItem = item as IDisposable;
                    if (disposableItem != null) disposableItem.Dispose();
                }
                m_Items = null;
            }
        }
        #endregion
    }
}' not found to remove
Parameter name: syntaxTree'.	RjBuildTest		1	Active

@jcurl
Copy link
Author

jcurl commented Sep 9, 2018

The version of NuGet.Frameworks that I'm using is v4.6.2 and the target framework is .NET 4.5, so it's pulling in the version .NETFramework,Version=v4.0

@Evangelink
Copy link
Contributor

I have tried a lot of combination but I really don't manage to reproduce the issue. Could you share a full project reproducing the error?

@jcurl
Copy link
Author

jcurl commented Sep 25, 2018

Hi, do you have some suggestions that i can zip the project and provide privately? I could provide a second set also with lots of exceptions, bit can't post it here.

@valhristov
Copy link
Contributor

Hi @jcurl, you could upload the zip file here. This is a Dropbox file request, an upload-only link, no one can download the files except me.

@duncanp-lseg
Copy link
Contributor

@jcurl, thanks for reporting the issue. The bug is fixed in next version of SonarC#.

FYI we had trouble reproducing the issue ourselves because it only occurs in the IDE and only if the base class and child classes are different projects. It was easy to repro with the projects you uploaded, so thanks again for that.

@jcurl
Copy link
Author

jcurl commented Oct 4, 2018

Great! Glad to be of help. I've got another project with 54 of these warnings, so looking forward to the correction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Bug Exceptions and blocking issues during analysis.
Projects
None yet
Development

No branches or pull requests

4 participants