diff options
Diffstat (limited to 'lib/Utils')
-rw-r--r-- | lib/Utils/src/Memory/PrivateString.cs | 182 | ||||
-rw-r--r-- | lib/Utils/src/Memory/PrivateStringManager.cs | 116 |
2 files changed, 146 insertions, 152 deletions
diff --git a/lib/Utils/src/Memory/PrivateString.cs b/lib/Utils/src/Memory/PrivateString.cs index 20d658a..8300b97 100644 --- a/lib/Utils/src/Memory/PrivateString.cs +++ b/lib/Utils/src/Memory/PrivateString.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -27,15 +27,33 @@ using System.Diagnostics.CodeAnalysis; namespace VNLib.Utils.Memory { + /// <summary> /// Provides a wrapper class that will have unsafe access to the memory of /// the specified <see cref="string"/> provided during object creation. /// </summary> /// <remarks>The value of the memory the protected string points to is undefined when the instance is disposed</remarks> - public class PrivateString : PrivateStringManager, IEquatable<PrivateString>, IEquatable<string>, ICloneable + public class PrivateString : + PrivateStringManager, + IEquatable<PrivateString>, + IEquatable<string>, + ICloneable { - protected string StrRef => base[0]!; - private readonly bool OwnsReferrence; + /// <summary> + /// Gets the internal string referrence + /// </summary> + protected string StringRef => base[0]!; + + /// <summary> + /// Does the current instance "own" the memory the data parameter points to + /// </summary> + protected bool OwnsReferrence { get; } + + /// <summary> + /// The internal string's length + /// </summary> + /// <exception cref="ObjectDisposedException"></exception> + public int Length => StringRef.Length; /// <summary> /// Creates a new <see cref="PrivateString"/> over the specified string and the memory it points to. @@ -43,130 +61,60 @@ namespace VNLib.Utils.Memory /// <param name="data">The <see cref="string"/> instance pointing to the memory to protect</param> /// <param name="ownsReferrence">Does the current instance "own" the memory the data parameter points to</param> /// <remarks>You should no longer reference the input string directly</remarks> - public PrivateString(string data, bool ownsReferrence = true) : base(1) + /// <exception cref="ArgumentException"></exception> + public PrivateString(string data, bool ownsReferrence) : base(1) { //Create a private string manager to store referrence to string base[0] = data ?? throw new ArgumentNullException(nameof(data)); OwnsReferrence = ownsReferrence; } - //Create private string from a string - public static explicit operator PrivateString?(string? data) - { - //Allow passing null strings during implicit casting - return data == null ? null : new(data); - } - - public static PrivateString? ToPrivateString(string? value) - { - return value == null ? null : new PrivateString(value, true); - } - - //Cast to string - public static explicit operator string (PrivateString str) - { - //Check if disposed, or return the string - str.Check(); - return str.StrRef; - } - - public static implicit operator ReadOnlySpan<char>(PrivateString str) - { - return str.Disposed ? Span<char>.Empty : str.StrRef.AsSpan(); - } - /// <summary> /// Gets the value of the internal string as a <see cref="ReadOnlySpan{T}"/> /// </summary> /// <returns>The <see cref="ReadOnlySpan{T}"/> referrence to the internal string</returns> /// <exception cref="ObjectDisposedException"></exception> - public ReadOnlySpan<char> ToReadOnlySpan() - { - Check(); - return StrRef.AsSpan(); - } + public ReadOnlySpan<char> ToReadOnlySpan() => StringRef.AsSpan(); + + /// <summary> + /// Creates a new deep copy of the current instance that + /// is an independent <see cref="PrivateString"/> + /// </summary> + /// <returns>The new <see cref="PrivateString"/> instance</returns> + /// <exception cref="ObjectDisposedException"></exception> + public virtual PrivateString Clone() => new(ToString(), true); ///<inheritdoc/> - public bool Equals(string? other) - { - Check(); - return StrRef.Equals(other, StringComparison.Ordinal); - } + public bool Equals(string? other) => StringRef.Equals(other, StringComparison.Ordinal); + ///<inheritdoc/> - public bool Equals(PrivateString? other) - { - Check(); - return other != null && StrRef.Equals(other.StrRef, StringComparison.Ordinal); - } + public bool Equals(PrivateString? other) => other is not null && StringRef.Equals(other.StringRef, StringComparison.Ordinal); + ///<inheritdoc/> - public override bool Equals(object? obj) - { - Check(); - return obj is PrivateString otherRef && StrRef.Equals(otherRef); - } + public override bool Equals(object? obj) => obj is PrivateString otherRef && StringRef.Equals(otherRef); + ///<inheritdoc/> - public bool Equals(ReadOnlySpan<char> other) - { - Check(); - return StrRef.AsSpan().SequenceEqual(other); - } + public bool Equals(ReadOnlySpan<char> other) => StringRef.AsSpan().SequenceEqual(other); + /// <summary> /// Creates a deep copy of the internal string and returns that copy /// </summary> /// <returns>A deep copy of the internal string</returns> - public override string ToString() - { - Check(); - return new(StrRef.AsSpan()); - } - /// <summary> - /// String length - /// </summary> - /// <exception cref="ObjectDisposedException"></exception> - public int Length - { - get - { - Check(); - return StrRef.Length; - } - } - /// <summary> - /// Indicates whether the underlying string is null or an empty string ("") - /// </summary> - /// <param name="ps"></param> - /// <returns>True if the parameter is null, or an empty string (""). False otherwise</returns> - public static bool IsNullOrEmpty([NotNullWhen(false)] PrivateString? ps) => ps == null|| ps.Length == 0; + public override string ToString() => CopyStringAtIndex(0)!; /// <summary> /// The hashcode of the underlying string /// </summary> /// <returns></returns> - public override int GetHashCode() - { - Check(); - return StrRef.GetHashCode(StringComparison.Ordinal); - } + public override int GetHashCode() => Disposed ? 0 : string.GetHashCode(StringRef, StringComparison.Ordinal); /// <summary> - /// Creates a new deep copy of the current instance that is an independent <see cref="PrivateString"/> + /// Creates a new deep copy of the current instance that + /// is an independent <see cref="PrivateString"/> /// </summary> /// <returns>The new <see cref="PrivateString"/> instance</returns> /// <exception cref="ObjectDisposedException"></exception> - public override object Clone() - { - Check(); - //Copy all contents of string to another reference - string clone = new (StrRef.AsSpan()); - //return a new private string - return new PrivateString(clone, true); - } - - ///<inheritdoc/> - protected override void Free() - { - Erase(); - } + object ICloneable.Clone() => new PrivateString(ToString(), true); /// <summary> /// Erases the contents of the internal CLR string @@ -179,5 +127,43 @@ namespace VNLib.Utils.Memory base.Free(); } } + + ///<inheritdoc/> + protected override void Free() => Erase(); + + /// <summary> + /// Indicates whether the underlying string is null or an empty string ("") + /// </summary> + /// <param name="ps"></param> + /// <returns>True if the parameter is null, or an empty string (""). False otherwise</returns> + public static bool IsNullOrEmpty([NotNullWhen(false)] PrivateString? ps) => ps is null || ps.Length == 0; + + /// <summary> + /// A nullable cast to a <see cref="PrivateString"/> + /// </summary> + /// <param name="data"></param> + public static explicit operator PrivateString?(string? data) => ToPrivateString(data, true); + + /// <summary> + /// Creates a new <see cref="PrivateString"/> if the data is not null that owns the memory + /// the string points to, null otherwise. + /// </summary> + /// <param name="data">The string reference to wrap</param> + /// <param name="ownsString">A value that indicates if the string memory is owned by the instance</param> + /// <returns>The new private string wrapper, or null if the value is null</returns> + public static PrivateString? ToPrivateString(string? data, bool ownsString) => data == null ? null : new(data, ownsString); + + /// <summary> + /// Casts the <see cref="PrivateString"/> to a <see cref="string"/> + /// </summary> + /// <param name="str"></param> + public static explicit operator string?(PrivateString? str) => str?.StringRef; + + /// <summary> + /// Casts the <see cref="PrivateString"/> to a <see cref="ReadOnlySpan{T}"/> + /// </summary> + /// <param name="str"></param> + public static implicit operator ReadOnlySpan<char>(PrivateString? str) => (str is null || str.Disposed) ? Span<char>.Empty : str.StringRef.AsSpan(); + } } diff --git a/lib/Utils/src/Memory/PrivateStringManager.cs b/lib/Utils/src/Memory/PrivateStringManager.cs index 8f01e98..3d50463 100644 --- a/lib/Utils/src/Memory/PrivateStringManager.cs +++ b/lib/Utils/src/Memory/PrivateStringManager.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -24,24 +24,25 @@ using System; - namespace VNLib.Utils.Memory { /// <summary> /// When inherited by a class, provides a safe string storage that zeros a CLR string memory on disposal /// </summary> - public class PrivateStringManager : VnDisposeable, ICloneable + public class PrivateStringManager : VnDisposeable { + private readonly StringRef[] ProtectedElements; + /// <summary> - /// Strings to be cleared when exiting + /// Create a new instance with fixed array size /// </summary> - private readonly string?[] ProtectedElements; + /// <param name="elements">Number of elements to protect</param> + public PrivateStringManager(int elements) => ProtectedElements = new StringRef[elements]; + /// <summary> /// Gets or sets a string referrence into the protected elements store /// </summary> - /// <param name="index"></param> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="ArgumentNullException"></exception> + /// <param name="index">The table index to store the string</param> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <returns>Referrence to string associated with the index</returns> @@ -50,68 +51,75 @@ namespace VNLib.Utils.Memory get { Check(); - return ProtectedElements[index]; + return ProtectedElements[index].Value; } set { Check(); - //Check to see if the string has been interned - if (!string.IsNullOrEmpty(value) && string.IsInterned(value) != null) - { - throw new ArgumentException($"The specified string has been CLR interned and cannot be stored in {nameof(PrivateStringManager)}"); - } - //Clear the old value before setting the new one - if (!string.IsNullOrEmpty(ProtectedElements[index])) - { - MemoryUtil.UnsafeZeroMemory<char>(ProtectedElements[index]); - } - //set new value - ProtectedElements[index] = value; + SetValue(index, value); } } - /// <summary> - /// Create a new instance with fixed array size - /// </summary> - /// <param name="elements">Number of elements to protect</param> - public PrivateStringManager(int elements) + + private void SetValue(int index, string? value) { - //Allocate the string array - ProtectedElements = new string[elements]; + //Try to get the old reference and erase it + StringRef strRef = ProtectedElements[index]; + strRef.Erase(); + + //Set the new value and determine if it is interned + ProtectedElements[index] = value is null ? + new StringRef(null, false) + : new StringRef(value, string.IsInterned(value) != null); } - ///<inheritdoc/> - protected override void Free() + + /// <summary> + /// Gets a copy of the string at the specified index. The + /// value returned is safe from erasure and is an independent + /// string + /// </summary> + /// <param name="index">The index to get the copy of the string at</param> + /// <returns>The copied string instance</returns> + protected string? CopyStringAtIndex(int index) { - //Zero all strings specified - for (int i = 0; i < ProtectedElements.Length; i++) + Check(); + StringRef str = ProtectedElements[index]; + + if(str.Value is null) { - if (!string.IsNullOrEmpty(ProtectedElements[i])) - { - //Zero the string memory - MemoryUtil.UnsafeZeroMemory<char>(ProtectedElements[i]); - //Set to null - ProtectedElements[i] = null; - } + //Pass null + return null; + } + else if (str.IsInterned) + { + /* + * If string is interned, it is safe to return the + * string referrence as it will not be erased + */ + return str.Value; + } + else + { + //Copy to new clr string + return str.Value.AsSpan().ToString(); } } - /// <summary> - /// Creates a deep copy for a new independent <see cref="PrivateStringManager"/> - /// </summary> - /// <returns>A new independent <see cref="PrivateStringManager"/> instance</returns> - /// <remarks>Be careful duplicating large instances, and make sure clones are properly disposed if necessary</remarks> - /// <exception cref="ObjectDisposedException"></exception> - public virtual object Clone() + ///<inheritdoc/> + protected override void Free() => Array.ForEach(ProtectedElements, static p => p.Erase()); + + private readonly record struct StringRef(string? Value, bool IsInterned) { - Check(); - PrivateStringManager other = new (ProtectedElements.Length); - //Copy all strings to the other instance - for(int i = 0; i < ProtectedElements.Length; i++) + public readonly void Erase() { - //Copy all strings and store their copies in the new array - other.ProtectedElements[i] = this.ProtectedElements[i].AsSpan().ToString(); + /* + * Only erase if the string is not interned + * and is not null + */ + if (Value is not null && !IsInterned) + { + MemoryUtil.UnsafeZeroMemory<char>(Value); + } } - //return the new copy - return other; } } } |