diff options
17 files changed, 368 insertions, 267 deletions
diff --git a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs index c8326d80..54c44e47 100644 --- a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs +++ b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs @@ -320,38 +320,18 @@ namespace Google.Protobuf Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(data65));
- CodedInputStream input = data64.CreateCodedInput();
- input.SetRecursionLimit(8);
+ CodedInputStream input = CodedInputStream.CreateWithLimits(new MemoryStream(data64.ToByteArray()), 1000000, 63);
Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(input));
}
- /*
[Test]
public void SizeLimit()
{
// Have to use a Stream rather than ByteString.CreateCodedInput as SizeLimit doesn't
// apply to the latter case.
- MemoryStream ms = new MemoryStream(TestUtil.GetAllSet().ToByteString().ToByteArray());
- CodedInputStream input = new CodedInputStream(ms);
- input.SetSizeLimit(16);
-
- Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.ParseFrom(input));
- }*/
-
- [Test]
- public void ResetSizeCounter()
- {
- CodedInputStream input = new CodedInputStream(
- new SmallBlockInputStream(new byte[256], 8));
- input.SetSizeLimit(16);
- input.ReadRawBytes(16);
-
- Assert.Throws<InvalidProtocolBufferException>(() => input.ReadRawByte());
-
- input.ResetSizeCounter();
- input.ReadRawByte(); // No exception thrown.
-
- Assert.Throws<InvalidProtocolBufferException>(() => input.ReadRawBytes(16));
+ MemoryStream ms = new MemoryStream(SampleMessages.CreateFullTestAllTypes().ToByteArray());
+ CodedInputStream input = CodedInputStream.CreateWithLimits(ms, 16, 100);
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(input));
}
/// <summary>
@@ -423,7 +403,7 @@ namespace Google.Protobuf output.Flush();
ms.Position = 0;
- CodedInputStream input = new CodedInputStream(ms, new byte[ms.Length / 2]);
+ CodedInputStream input = new CodedInputStream(ms, new byte[ms.Length / 2], 0, 0);
uint tag = input.ReadTag();
Assert.AreEqual(1, WireFormat.GetTagFieldNumber(tag));
@@ -528,5 +508,23 @@ namespace Google.Protobuf Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.StartGroup), input.ReadTag());
Assert.Throws<InvalidProtocolBufferException>(() => input.SkipLastField());
}
+
+ [Test]
+ public void Construction_Invalid()
+ {
+ Assert.Throws<ArgumentNullException>(() => new CodedInputStream((byte[]) null));
+ Assert.Throws<ArgumentNullException>(() => new CodedInputStream(null, 0, 0));
+ Assert.Throws<ArgumentNullException>(() => new CodedInputStream((Stream) null));
+ Assert.Throws<ArgumentOutOfRangeException>(() => new CodedInputStream(new byte[10], 100, 0));
+ Assert.Throws<ArgumentOutOfRangeException>(() => new CodedInputStream(new byte[10], 5, 10));
+ }
+
+ [Test]
+ public void CreateWithLimits_InvalidLimits()
+ {
+ var stream = new MemoryStream();
+ Assert.Throws<ArgumentOutOfRangeException>(() => CodedInputStream.CreateWithLimits(stream, 0, 1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => CodedInputStream.CreateWithLimits(stream, 1, 0));
+ }
}
}
\ No newline at end of file diff --git a/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs index c1bf7bd6..3297fe87 100644 --- a/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs +++ b/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs @@ -334,7 +334,7 @@ namespace Google.Protobuf }
// Now test Input stream:
{
- CodedInputStream cin = new CodedInputStream(new MemoryStream(bytes), new byte[50]);
+ CodedInputStream cin = new CodedInputStream(new MemoryStream(bytes), new byte[50], 0, 0);
Assert.AreEqual(0, cin.Position);
// Field 1:
uint tag = cin.ReadTag();
diff --git a/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs b/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs index c62ac046..29c4c2a9 100644 --- a/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs +++ b/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs @@ -477,6 +477,91 @@ namespace Google.Protobuf.Collections Assert.IsTrue(new MapField<int, int?>(true).AllowsNullValues); } + [Test] + public void KeysReturnsLiveView() + { + var map = new MapField<string, string>(); + var keys = map.Keys; + CollectionAssert.AreEqual(new string[0], keys); + map["foo"] = "bar"; + map["x"] = "y"; + CollectionAssert.AreEqual(new[] { "foo", "x" }, keys); + } + + [Test] + public void ValuesReturnsLiveView() + { + var map = new MapField<string, string>(); + var values = map.Values; + CollectionAssert.AreEqual(new string[0], values); + map["foo"] = "bar"; + map["x"] = "y"; + CollectionAssert.AreEqual(new[] { "bar", "y" }, values); + } + + // Just test keys - we know the implementation is the same for values + [Test] + public void ViewsAreReadOnly() + { + var map = new MapField<string, string>(); + var keys = map.Keys; + Assert.IsTrue(keys.IsReadOnly); + Assert.Throws<NotSupportedException>(() => keys.Clear()); + Assert.Throws<NotSupportedException>(() => keys.Remove("a")); + Assert.Throws<NotSupportedException>(() => keys.Add("a")); + } + + // Just test keys - we know the implementation is the same for values + [Test] + public void ViewCopyTo() + { + var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; + var keys = map.Keys; + var array = new string[4]; + Assert.Throws<ArgumentException>(() => keys.CopyTo(array, 3)); + Assert.Throws<ArgumentOutOfRangeException>(() => keys.CopyTo(array, -1)); + keys.CopyTo(array, 1); + CollectionAssert.AreEqual(new[] { null, "foo", "x", null }, array); + } + + // Just test keys - we know the implementation is the same for values + [Test] + public void NonGenericViewCopyTo() + { + IDictionary map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; + ICollection keys = map.Keys; + // Note the use of the Array type here rather than string[] + Array array = new string[4]; + Assert.Throws<ArgumentException>(() => keys.CopyTo(array, 3)); + Assert.Throws<ArgumentOutOfRangeException>(() => keys.CopyTo(array, -1)); + keys.CopyTo(array, 1); + CollectionAssert.AreEqual(new[] { null, "foo", "x", null }, array); + } + + [Test] + public void KeysContains() + { + var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; + var keys = map.Keys; + Assert.IsTrue(keys.Contains("foo")); + Assert.IsFalse(keys.Contains("bar")); // It's a value! + Assert.IsFalse(keys.Contains("1")); + // Keys can't be null, so we should prevent contains check + Assert.Throws<ArgumentNullException>(() => keys.Contains(null)); + } + + [Test] + public void ValuesContains() + { + var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; + var values = map.Values; + Assert.IsTrue(values.Contains("bar")); + Assert.IsFalse(values.Contains("foo")); // It's a key! + Assert.IsFalse(values.Contains("1")); + // Values can be null, so this makes sense + Assert.IsFalse(values.Contains(null)); + } + private static KeyValuePair<TKey, TValue> NewKeyValuePair<TKey, TValue>(TKey key, TValue value) { return new KeyValuePair<TKey, TValue>(key, value); diff --git a/csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs index 6e1d804e..936e41c6 100644 --- a/csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs +++ b/csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs @@ -213,6 +213,6 @@ namespace Google.Protobuf.Reflection var descriptor = TestAllTypes.Descriptor; Assert.Throws<KeyNotFoundException>(() => descriptor.Fields[999999].ToString()); Assert.Throws<KeyNotFoundException>(() => descriptor.Fields["not found"].ToString()); - } + } } } diff --git a/csharp/src/Google.Protobuf/CodedInputStream.cs b/csharp/src/Google.Protobuf/CodedInputStream.cs index c8b33b33..82c6ceab 100644 --- a/csharp/src/Google.Protobuf/CodedInputStream.cs +++ b/csharp/src/Google.Protobuf/CodedInputStream.cs @@ -53,16 +53,13 @@ namespace Google.Protobuf /// </remarks>
public sealed class CodedInputStream
{
- // TODO(jonskeet): Consider whether recursion and size limits shouldn't be readonly,
- // set at construction time.
-
/// <summary>
/// Buffer of data read from the stream or provided at construction time.
/// </summary>
private readonly byte[] buffer;
/// <summary>
- /// The number of valid bytes in the buffer.
+ /// The index of the buffer at which we need to refill from the stream (if there is one).
/// </summary>
private int bufferSize;
@@ -106,62 +103,104 @@ namespace Google.Protobuf /// </summary>
private int currentLimit = int.MaxValue;
- /// <summary>
- /// <see cref="SetRecursionLimit"/>
- /// </summary>
private int recursionDepth = 0;
- private int recursionLimit = DefaultRecursionLimit;
+ private readonly int recursionLimit;
+ private readonly int sizeLimit;
+
+ #region Construction
+ // Note that the checks are performed such that we don't end up checking obviously-valid things
+ // like non-null references for arrays we've just created.
/// <summary>
- /// <see cref="SetSizeLimit"/>
+ /// Creates a new CodedInputStream reading data from the given byte array.
/// </summary>
- private int sizeLimit = DefaultSizeLimit;
+ public CodedInputStream(byte[] buffer) : this(null, Preconditions.CheckNotNull(buffer, "buffer"), 0, buffer.Length)
+ {
+ }
- #region Construction
/// <summary>
- /// Creates a new CodedInputStream reading data from the given
- /// byte array.
+ /// Creates a new CodedInputStream that reads from the given byte array slice.
/// </summary>
- public CodedInputStream(byte[] buf) : this(buf, 0, buf.Length)
- {
+ public CodedInputStream(byte[] buffer, int offset, int length)
+ : this(null, Preconditions.CheckNotNull(buffer, "buffer"), offset, offset + length)
+ {
+ if (offset < 0 || offset > buffer.Length)
+ {
+ throw new ArgumentOutOfRangeException("offset", "Offset must be within the buffer");
+ }
+ if (length < 0 || offset + length > buffer.Length)
+ {
+ throw new ArgumentOutOfRangeException("length", "Length must be non-negative and within the buffer");
+ }
}
/// <summary>
- /// Creates a new CodedInputStream that reads from the given
- /// byte array slice.
+ /// Creates a new CodedInputStream reading data from the given stream.
/// </summary>
- public CodedInputStream(byte[] buffer, int offset, int length)
+ public CodedInputStream(Stream input) : this(input, new byte[BufferSize], 0, 0)
{
- this.buffer = buffer;
- this.bufferPos = offset;
- this.bufferSize = offset + length;
- this.input = null;
+ Preconditions.CheckNotNull(input, "input");
}
/// <summary>
- /// Creates a new CodedInputStream reading data from the given stream.
+ /// Creates a new CodedInputStream reading data from the given
+ /// stream and buffer, using the default limits.
/// </summary>
- public CodedInputStream(Stream input)
+ internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize)
{
- this.buffer = new byte[BufferSize];
- this.bufferSize = 0;
this.input = input;
+ this.buffer = buffer;
+ this.bufferPos = bufferPos;
+ this.bufferSize = bufferSize;
+ this.sizeLimit = DefaultSizeLimit;
+ this.recursionLimit = DefaultRecursionLimit;
}
/// <summary>
/// Creates a new CodedInputStream reading data from the given
- /// stream, with a pre-allocated buffer.
+ /// stream and buffer, using the specified limits.
/// </summary>
- internal CodedInputStream(Stream input, byte[] buffer)
+ /// <remarks>
+ /// This chains to the version with the default limits instead of vice versa to avoid
+ /// having to check that the default values are valid every time.
+ /// </remarks>
+ internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize, int sizeLimit, int recursionLimit)
+ : this(input, buffer, bufferPos, bufferSize)
{
- this.buffer = buffer;
- this.bufferSize = 0;
- this.input = input;
+ if (sizeLimit <= 0)
+ {
+ throw new ArgumentOutOfRangeException("sizeLimit", "Size limit must be positive");
+ }
+ if (recursionLimit <= 0)
+ {
+ throw new ArgumentOutOfRangeException("recursionLimit!", "Recursion limit must be positive");
+ }
+ this.sizeLimit = sizeLimit;
+ this.recursionLimit = recursionLimit;
}
#endregion
/// <summary>
+ /// Creates a <see cref="CodedInputStream"/> with the specified size and recursion limits, reading
+ /// from an input stream.
+ /// </summary>
+ /// <remarks>
+ /// This method exists separately from the constructor to reduce the number of constructor overloads.
+ /// It is likely to be used considerably less frequently than the constructors, as the default limits
+ /// are suitable for most use cases.
+ /// </remarks>
+ /// <param name="input">The input stream to read from</param>
+ /// <param name="sizeLimit">The total limit of data to read from the stream.</param>
+ /// <param name="recursionLimit">The maximum recursion depth to allow while reading.</param>
+ /// <returns>A <c>CodedInputStream</c> reading from <paramref name="input"/> with the specified size
+ /// and recursion limits.</returns>
+ public static CodedInputStream CreateWithLimits(Stream input, int sizeLimit, int recursionLimit)
+ {
+ return new CodedInputStream(input, new byte[BufferSize], 0, 0, sizeLimit, recursionLimit);
+ }
+
+ /// <summary>
/// Returns the current position in the input stream, or the position in the input buffer
/// </summary>
public long Position
@@ -182,59 +221,30 @@ namespace Google.Protobuf /// </summary>
internal uint LastTag { get { return lastTag; } }
- #region Limits for recursion and length
/// <summary>
- /// Set the maximum message recursion depth.
+ /// Returns the size limit for this stream.
/// </summary>
/// <remarks>
- /// In order to prevent malicious
- /// messages from causing stack overflows, CodedInputStream limits
- /// how deeply messages may be nested. The default limit is 64.
+ /// This limit is applied when reading from the underlying stream, as a sanity check. It is
+ /// not applied when reading from a byte array data source without an underlying stream.
+ /// The default value is 64MB.
/// </remarks>
- public int SetRecursionLimit(int limit)
- {
- if (limit < 0)
- {
- throw new ArgumentOutOfRangeException("Recursion limit cannot be negative: " + limit);
- }
- int oldLimit = recursionLimit;
- recursionLimit = limit;
- return oldLimit;
- }
+ /// <value>
+ /// The size limit.
+ /// </value>
+ public int SizeLimit { get { return sizeLimit; } }
/// <summary>
- /// Set the maximum message size.
+ /// Returns the recursion limit for this stream. This limit is applied whilst reading messages,
+ /// to avoid maliciously-recursive data.
/// </summary>
/// <remarks>
- /// In order to prevent malicious messages from exhausting memory or
- /// causing integer overflows, CodedInputStream limits how large a message may be.
- /// The default limit is 64MB. You should set this limit as small
- /// as you can without harming your app's functionality. Note that
- /// size limits only apply when reading from an InputStream, not
- /// when constructed around a raw byte array (nor with ByteString.NewCodedInput).
- /// If you want to read several messages from a single CodedInputStream, you
- /// can call ResetSizeCounter() after each message to avoid hitting the
- /// size limit.
+ /// The default limit is 64.
/// </remarks>
- public int SetSizeLimit(int limit)
- {
- if (limit < 0)
- {
- throw new ArgumentOutOfRangeException("Size limit cannot be negative: " + limit);
- }
- int oldLimit = sizeLimit;
- sizeLimit = limit;
- return oldLimit;
- }
-
- /// <summary>
- /// Resets the current size counter to zero (see <see cref="SetSizeLimit"/>).
- /// </summary>
- public void ResetSizeCounter()
- {
- totalBytesRetired = 0;
- }
- #endregion
+ /// <value>
+ /// The recursion limit for this stream.
+ /// </value>
+ public int RecursionLimit { get { return recursionLimit; } }
#region Validation
/// <summary>
diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs index dc4b04cb..0fa63bef 100644 --- a/csharp/src/Google.Protobuf/Collections/MapField.cs +++ b/csharp/src/Google.Protobuf/Collections/MapField.cs @@ -136,6 +136,12 @@ namespace Google.Protobuf.Collections return map.ContainsKey(key); } + private bool ContainsValue(TValue value) + { + var comparer = EqualityComparer<TValue>.Default; + return list.Any(pair => comparer.Equals(pair.Value, value)); + } + /// <summary> /// Removes the entry identified by the given key from the map. /// </summary> @@ -221,17 +227,15 @@ namespace Google.Protobuf.Collections } } - // TODO: Make these views? - /// <summary> /// Gets a collection containing the keys in the map. /// </summary> - public ICollection<TKey> Keys { get { return list.Select(t => t.Key).ToList(); } } + public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } } /// <summary> /// Gets a collection containing the values in the map. /// </summary> - public ICollection<TValue> Values { get { return list.Select(t => t.Value).ToList(); } } + public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } } /// <summary> /// Adds the specified entries to the map. @@ -658,5 +662,92 @@ namespace Google.Protobuf.Collections MessageDescriptor IMessage.Descriptor { get { return null; } } } } + + private class MapView<T> : ICollection<T>, ICollection + { + private readonly MapField<TKey, TValue> parent; + private readonly Func<KeyValuePair<TKey, TValue>, T> projection; + private readonly Func<T, bool> containsCheck; + + internal MapView( + MapField<TKey, TValue> parent, + Func<KeyValuePair<TKey, TValue>, T> projection, + Func<T, bool> containsCheck) + { + this.parent = parent; + this.projection = projection; + this.containsCheck = containsCheck; + } + + public int Count { get { return parent.Count; } } + + public bool IsReadOnly { get { return true; } } + + public bool IsSynchronized { get { return false; } } + + public object SyncRoot { get { return parent; } } + + public void Add(T item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(T item) + { + return containsCheck(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException("arrayIndex"); + } + if (arrayIndex + Count >= array.Length) + { + throw new ArgumentException("Not enough space in the array", "array"); + } + foreach (var item in this) + { + array[arrayIndex++] = item; + } + } + + public IEnumerator<T> GetEnumerator() + { + return parent.list.Select(projection).GetEnumerator(); + } + + public bool Remove(T item) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void CopyTo(Array array, int index) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + if (index + Count >= array.Length) + { + throw new ArgumentException("Not enough space in the array", "array"); + } + foreach (var item in this) + { + array.SetValue(item, index++); + } + } + } } } diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj index 033edfbb..a17bf81c 100644 --- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj +++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj @@ -83,6 +83,7 @@ <Compile Include="Compatibility\TypeExtensions.cs" />
<Compile Include="FieldCodec.cs" />
<Compile Include="FrameworkPortability.cs" />
+ <Compile Include="IDeepCloneable.cs" />
<Compile Include="JsonFormatter.cs" />
<Compile Include="MessageExtensions.cs" />
<Compile Include="IMessage.cs" />
diff --git a/csharp/src/Google.Protobuf/IDeepCloneable.cs b/csharp/src/Google.Protobuf/IDeepCloneable.cs new file mode 100644 index 00000000..c9c71bbe --- /dev/null +++ b/csharp/src/Google.Protobuf/IDeepCloneable.cs @@ -0,0 +1,54 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +namespace Google.Protobuf +{ + /// <summary> + /// Generic interface for a deeply cloneable type. + /// </summary> + /// <remarks> + /// <para> + /// All generated messages implement this interface, but so do some non-message types. + /// Additionally, due to the type constraint on <c>T</c> in <see cref="IMessage{T}"/>, + /// it is simpler to keep this as a separate interface. + /// </para> + /// </remarks> + /// <typeparam name="T">The type itself, returned by the <see cref="Clone"/> method.</typeparam> + public interface IDeepCloneable<T> + { + /// <summary> + /// Creates a deep clone of this object. + /// </summary> + /// <returns>A deep clone of this object.</returns> + T Clone(); + } +} diff --git a/csharp/src/Google.Protobuf/IMessage.cs b/csharp/src/Google.Protobuf/IMessage.cs index 773845ff..d089f946 100644 --- a/csharp/src/Google.Protobuf/IMessage.cs +++ b/csharp/src/Google.Protobuf/IMessage.cs @@ -35,8 +35,6 @@ using Google.Protobuf.Reflection; namespace Google.Protobuf
{
- // TODO(jonskeet): Split these interfaces into separate files when we're happy with them.
-
/// <summary>
/// Interface for a Protocol Buffers message, supporting
/// basic operations required for serialization.
@@ -86,24 +84,4 @@ namespace Google.Protobuf /// <param name="message">The message to merge with this one. Must not be null.</param>
void MergeFrom(T message);
}
-
- /// <summary>
- /// Generic interface for a deeply cloneable type.
- /// </summary>
- /// <remarks>
- /// <para>
- /// All generated messages implement this interface, but so do some non-message types.
- /// Additionally, due to the type constraint on <c>T</c> in <see cref="IMessage{T}"/>,
- /// it is simpler to keep this as a separate interface.
- /// </para>
- /// </remarks>
- /// <typeparam name="T">The type itself, returned by the <see cref="Clone"/> method.</typeparam>
- public interface IDeepCloneable<T>
- {
- /// <summary>
- /// Creates a deep clone of this object.
- /// </summary>
- /// <returns>A deep clone of this object.</returns>
- T Clone();
- }
}
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs index 157ea400..759955e6 100644 --- a/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs +++ b/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs @@ -257,10 +257,12 @@ namespace Google.Protobuf.Reflection /// or unqualified. C++-like name lookup semantics are used to search for the /// matching descriptor. /// </summary> + /// <remarks> + /// This isn't heavily optimized, but it's only used during cross linking anyway. + /// If it starts being used more widely, we should look at performance more carefully. + /// </remarks> internal IDescriptor LookupSymbol(string name, IDescriptor relativeTo) { - // TODO(jonskeet): This could be optimized in a number of ways. - IDescriptor result; if (name.StartsWith(".")) { @@ -282,7 +284,6 @@ namespace Google.Protobuf.Reflection { // Chop off the last component of the scope. - // TODO(jonskeet): Make this more efficient. May not be worth using StringBuilder at all int dotpos = scopeToTry.ToString().LastIndexOf("."); if (dotpos == -1) { diff --git a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs index bb8e9bbb..901cbf47 100644 --- a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs @@ -168,14 +168,16 @@ namespace Google.Protobuf.Reflection get { return fieldType == FieldType.Message && messageType.Proto.Options != null && messageType.Proto.Options.MapEntry; } } - // TODO(jonskeet): Check whether this is correct with proto3, where we default to packed... - /// <summary> /// Returns <c>true</c> if this field is a packed, repeated field; <c>false</c> otherwise. /// </summary> public bool IsPacked { - get { return Proto.Options != null && Proto.Options.Packed; } + // Note the || rather than && here - we're effectively defaulting to packed, because that *is* + // the default in proto3, which is all we support. We may give the wrong result for the protos + // within descriptor.proto, but that's okay, as they're never exposed and we don't use IsPacked + // within the runtime. + get { return Proto.Options == null || Proto.Options.Packed; } } /// <summary> diff --git a/csharp/src/Google.Protobuf/Reflection/PartialClasses.cs b/csharp/src/Google.Protobuf/Reflection/PartialClasses.cs index c7ed4342..8c055d6d 100644 --- a/csharp/src/Google.Protobuf/Reflection/PartialClasses.cs +++ b/csharp/src/Google.Protobuf/Reflection/PartialClasses.cs @@ -44,4 +44,16 @@ namespace Google.Protobuf.Reflection OneofIndex = -1;
}
}
+
+ internal partial class FieldOptions
+ {
+ // We can't tell the difference between "explicitly set to false" and "not set"
+ // in proto3, but we need to tell the difference for FieldDescriptor.IsPacked.
+ // This won't work if we ever need to support proto2, but at that point we'll be
+ // able to remove this hack and use field presence instead.
+ partial void OnConstruction()
+ {
+ Packed = true;
+ }
+ }
}
\ No newline at end of file diff --git a/objectivec/GPBWellKnownTypes.h b/objectivec/GPBWellKnownTypes.h index 050f85f6..28442fbe 100644 --- a/objectivec/GPBWellKnownTypes.h +++ b/objectivec/GPBWellKnownTypes.h @@ -30,11 +30,9 @@ #import <Foundation/Foundation.h> -#import "google/protobuf/Any.pbobjc.h" #import "google/protobuf/Duration.pbobjc.h" #import "google/protobuf/Timestamp.pbobjc.h" - NS_ASSUME_NONNULL_BEGIN // Extension to GPBTimestamp to work with standard Foundation time/date types. @@ -51,22 +49,4 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)timeIntervalSince1970; @end -// Extension to GPBAny to support packing and unpacking for arbitrary messages. -@interface GPBAny (GPBWellKnownTypes) -// Initialize GPBAny instance with the given message. e.g., for google.protobuf.foo, type -// url will be "type.googleapis.com/google.protobuf.foo" and value will be serialized foo. -- (instancetype)initWithMessage:(GPBMessage*)message; -// Serialize the given message to the value in GPBAny. Type url will also be updated. -- (void)setMessage:(GPBMessage*)message; -// Parse the value in GPBAny to the given message. If messageClass message doesn't match the -// type url in GPBAny, nil is returned. -- (GPBMessage*)messageOfClass:(Class)messageClass; -// True if the given type matches the type url in GPBAny. -- (BOOL)wrapsMessageOfClass:(Class)messageClass; -@end - -// Common prefix of type url in GPBAny, which is @"type.googleapis.com/". All valid -// type urls in any should start with this prefix. -extern NSString *const GPBTypeGoogleApisComPrefix; - NS_ASSUME_NONNULL_END diff --git a/objectivec/GPBWellKnownTypes.m b/objectivec/GPBWellKnownTypes.m index b48f8a53..fe02f5de 100644 --- a/objectivec/GPBWellKnownTypes.m +++ b/objectivec/GPBWellKnownTypes.m @@ -115,52 +115,3 @@ static int32_t SecondsAndNanosFromTimeIntervalSince1970(NSTimeInterval time, } @end - -NSString *const GPBTypeGoogleApisComPrefix = @"type.googleapis.com/"; - -@implementation GPBAny (GBPWellKnownTypes) - -- (instancetype)initWithMessage:(GPBMessage*)message { - self = [super init]; - if (self) { - [self setMessage:message]; - } - return self; -} - -- (NSString*)typeName { - NSAssert([self.typeURL hasPrefix:GPBTypeGoogleApisComPrefix], - @"Invalid any type url (%@).", self.typeURL); - if (![self.typeURL hasPrefix:GPBTypeGoogleApisComPrefix]) { - return nil; - } - return [self.typeURL substringFromIndex:[GPBTypeGoogleApisComPrefix length]]; -} - -- (void)setMessage:(GPBMessage*)message { - self.typeURL = [GPBTypeGoogleApisComPrefix stringByAppendingString:message.descriptor.name]; - self.value = message.data; -} - -- (GPBMessage*)messageOfClass:(Class)messageClass { - if ([self wrapsMessageOfClass:messageClass]) { - GPBMessage* message = [messageClass message]; - [message mergeFromData:self.value extensionRegistry:nil]; - return message; - } else { - return nil; - } -} - -- (BOOL)wrapsMessageOfClass:(Class)messageClass { - NSAssert([messageClass isSubclassofClass:[GPBMessage class]], - @"Given class (%@) is not a subclass of GPBMessage", - [messageClass name]); - if (![messageClass isSubclassOfClass:[GPBMessage class]]) { - return NO; - } - return [[self typeName] isEqualToString:messageClass.descriptor.name]; -} - -@end - diff --git a/objectivec/Tests/GPBWellKnownTypesTest.m b/objectivec/Tests/GPBWellKnownTypesTest.m index 48c875aa..78f4e637 100644 --- a/objectivec/Tests/GPBWellKnownTypesTest.m +++ b/objectivec/Tests/GPBWellKnownTypesTest.m @@ -28,9 +28,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#import "google/protobuf/Unittest.pbobjc.h" #import "GPBWellKnownTypes.h" -#import "GPBTestUtilities.h" #import <XCTest/XCTest.h> @@ -40,7 +38,7 @@ static const NSTimeInterval kFutureOffsetInterval = 15000; // Nanosecond time accuracy static const NSTimeInterval kTimeAccuracy = 1e-9; -@interface WellKnownTypesTest : GPBTestCase +@interface WellKnownTypesTest : XCTestCase @end @implementation WellKnownTypesTest @@ -101,53 +99,4 @@ static const NSTimeInterval kTimeAccuracy = 1e-9; [duration2 release]; } -- (void)testAnyPackingAndUnpacking { - TestAllTypes *from = [TestAllTypes message]; - [self setAllFields:from repeatedCount:1]; - NSData *data = from.data; - - // Test initWithMessage - GPBAny *anyInited = [[GPBAny alloc] initWithMessage:from]; - XCTAssertEqualObjects( - [GPBTypeGoogleApisComPrefix stringByAppendingString:from.descriptor.name], - anyInited.typeURL); - XCTAssertEqualObjects(data, anyInited.value); - [anyInited release]; - - // Test setMessage. - GPBAny *any = [GPBAny message]; - [any setMessage:from]; - XCTAssertEqualObjects( - [GPBTypeGoogleApisComPrefix stringByAppendingString:from.descriptor.name], - any.typeURL); - XCTAssertEqualObjects(data, any.value); - - // Test messageOfClass - TestAllTypes *to = (TestAllTypes*)[any messageOfClass:[TestAllTypes class]]; - XCTAssertEqualObjects(from, to); - XCTAssertEqual([any messageOfClass:[ForeignMessage class]], nil); - XCTAssertEqual([[GPBAny message] messageOfClass:[TestAllTypes class]], nil); - - // Test setMessage with another type. - ForeignMessage *from2 = [ForeignMessage message]; - [any setMessage:from2]; - XCTAssertEqualObjects( - [GPBTypeGoogleApisComPrefix stringByAppendingString:from2.descriptor.name], - any.typeURL); - XCTAssertEqual(0UL, [any.value length]); - - // Test wrapsMessageOfClass - XCTAssertTrue([any wrapsMessageOfClass:[from2 class]]); - XCTAssertFalse([any wrapsMessageOfClass:[from class]]); -#if !defined(NS_BLOCK_ASSERTIONS) - // If assert is enabled, throw exception when the passed message class to - // wrapsMessageOfClass is not a child of GPBMessage. - XCTAssertThrows([any wrapsMessageOfClass:[NSString class]]); -#else - // If assert is disabled, return false when the passed message class to - // wrapsMessageOfClass is not a child of GPBMessage. - XCTAssertFalse([any wrapsMessageOfClass:[NSString class]]); -#endif -} - @end diff --git a/src/google/protobuf/compiler/csharp/csharp_field_base.cc b/src/google/protobuf/compiler/csharp/csharp_field_base.cc index 7e3bbeef..cd29bcf9 100644 --- a/src/google/protobuf/compiler/csharp/csharp_field_base.cc +++ b/src/google/protobuf/compiler/csharp/csharp_field_base.cc @@ -59,7 +59,7 @@ void FieldGeneratorBase::SetCommonFieldVariables( // repeated fields varies by wire format. The wire format is encoded in the bottom 3 bits, which // never effects the tag size. int tag_size = internal::WireFormat::TagSize(descriptor_->number(), descriptor_->type()); - uint tag = FixedMakeTag(descriptor_); + uint tag = internal::WireFormat::MakeTag(descriptor_); uint8 tag_array[5]; io::CodedOutputStream::WriteTagToArray(tag, tag_array); string tag_bytes = SimpleItoa(tag_array[0]); diff --git a/src/google/protobuf/compiler/csharp/csharp_helpers.cc b/src/google/protobuf/compiler/csharp/csharp_helpers.cc index 46f4fc33..d25dcba9 100644 --- a/src/google/protobuf/compiler/csharp/csharp_helpers.cc +++ b/src/google/protobuf/compiler/csharp/csharp_helpers.cc @@ -338,17 +338,6 @@ std::string FileDescriptorToBase64(const FileDescriptor* descriptor) { return StringToBase64(fdp_bytes); } -// TODO(jonskeet): Remove this when internal::WireFormat::MakeTag works -// properly... -// Workaround for issue #493 -uint FixedMakeTag(const FieldDescriptor* field) { - internal::WireFormatLite::WireType field_type = field->is_packed() - ? internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED - : internal::WireFormat::WireTypeForFieldType(field->type()); - - return internal::WireFormatLite::MakeTag(field->number(), field_type); -} - FieldGeneratorBase* CreateFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal) { switch (descriptor->type()) { |