public FieldInfo[] GetAllFieldsThatShouldBeSet(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            if (_requiredFieldCache.TryGetValue(type, out var cachedResult))
            {
                return(cachedResult);
            }

            return(_requiredFieldCache.GetOrAdd(type, _reader.GetAllFieldsThatShouldBeSet(type)));
        }
Exemple #2
0
        private object ReadNextObject(bool ignoreAnyInvalidTypes, bool toPopulateDeferredInstance)
        {
            var typeName = ReadNextTypeName(out var typeNameReferenceID);

            if (typeName == null)
            {
                throw new InvalidSerialisationDataFormatException("Null type names should not exist in object data since there is a Null binary serialisation data type");
            }

            // If the next value is a Reference ID then the writer had supportReferenceReuse and all object definitions for reference types (except strings) will start with a
            // Reference ID that will either be a new ID (followed by the object data) or an existing ID (followed by ObjectEnd)
            int?referenceID;
            var nextEntryType = ReadNextDataType();

            if (nextEntryType == BinarySerialisationDataType.ReferenceID8)
            {
                referenceID = ReadNext();
            }
            else if (nextEntryType == BinarySerialisationDataType.ReferenceID16)
            {
                referenceID = ReadNextInt16();
            }
            else if (nextEntryType == BinarySerialisationDataType.ReferenceID24)
            {
                referenceID = ReadNextInt24();
            }
            else if (nextEntryType == BinarySerialisationDataType.ReferenceID32)
            {
                referenceID = ReadNext();
            }
            else
            {
                referenceID = null;
            }
            object alreadyEncounteredReference;

            if (referenceID != null)
            {
                if (referenceID < 0)
                {
                    throw new InvalidSerialisationDataFormatException("Encountered negative Reference ID, invalid:" + referenceID);
                }
                if (_objectReferences.TryGetValue(referenceID.Value, out alreadyEncounteredReference))
                {
                    // This is an existing Reference ID so ensure that it's followed by ObjectEnd and return the existing reference.. unless it is an object reference that was
                    // created as a placeholder and now needs to be fully populated (whicch is only the case if BinarySerialisationWriter is optimised for wide circular references,
                    // as opposed to tree structures)
                    if (!toPopulateDeferredInstance)
                    {
                        nextEntryType = ReadNextDataType();
                        if (nextEntryType != BinarySerialisationDataType.ObjectEnd)
                        {
                            throw new InvalidSerialisationDataFormatException($"Expected {nameof(BinarySerialisationDataType.ObjectEnd)} was not encountered after reused reference");
                        }
                        return(alreadyEncounteredReference);
                    }
                    else
                    {
                        _haveEncounteredDeferredInitialisationObject = true;
                    }
                }
                nextEntryType = ReadNextDataType();
            }
            else
            {
                referenceID = null;
                alreadyEncounteredReference = null;
            }

            // If we have already encountered this type then we should have a BinarySerialisationReaderTypeReader instance prepared that loops through the field data quickly,
            // rather than having to perform the more expensive work below (with its type name resolution and field name lookups)
            // - Note: If the content that is being deserialised include deferred-population object references (which is the case if the BinarySerialisationWriter was set up
            //   to be optimised for wide circular references) then we can't use these optimised type builders because sometimes we need to create new populated instances and
            //   somtimes we have to populate initialised-but-unpopulated references)
            if (!_haveEncounteredDeferredInitialisationObject && _typeReaders.TryGetValue(typeNameReferenceID, out var typeReader))
            {
                var instance = typeReader.GetUninitialisedInstance();
                if (referenceID != null)
                {
                    // If there is an Object Reference ID for the current object then we need to push the uninitialised instance into the objectReferences dictionary before
                    // trying to populate because there is a chance that properties on it will include a circular references back to this object and if there is no
                    // objectReferences entry then that will cause much confusion (see BinarySerialisationTests_SupportReferenceReUseInMostlyTreeLikeStructure's
                    // "CircularReferencesAreSupportedWhereTheSameTypeIsEncounteredMultipleTimes" test method for an example)
                    _objectReferences[referenceID.Value] = instance;
                }
                return(typeReader.ReadInto(instance, this, nextEntryType, ignoreAnyInvalidTypes));
            }

            // Try to get a type builder for the type that we should be deserialising to. If ignoreAnyInvalidTypes is true then don't worry if we can't find the type that is
            // specified because we don't care about the return value from this method, we're just parsing the data to progress to the next data that we DO care about.
            var typeBuilderIfAvailable = _typeAnalyser.TryToGetUninitialisedInstanceBuilder(typeName);

            if ((typeBuilderIfAvailable == null) && !ignoreAnyInvalidTypes)
            {
                throw new TypeLoadException("Unable to load type " + typeName);
            }
            object valueIfTypeIsAvailable;

            if (toPopulateDeferredInstance)
            {
                // If this is a deferred-initialisation reference then we already have a reference but it hasn't been populated yet - so use that reference instead of
                // creating a new one
                valueIfTypeIsAvailable = alreadyEncounteredReference;
            }
            else
            {
                valueIfTypeIsAvailable = (typeBuilderIfAvailable == null) ? null : typeBuilderIfAvailable();
            }
            if ((valueIfTypeIsAvailable != null) && (referenceID != null))
            {
                // If this is data from a BinarySerialisationWriter that was optimised for wide circular references then some object references will be passed through twice;
                // once to create an non-populated placeholder and again to set all of the fields (but if the writer was optimised for trees then each object reference will
                // only ever be set once). Since there is a chance that we will encounter a reference twice, we COULD check ContainsKey and THEN call Add OR we could just
                // set the item once and do the lookup / find-insert-point only once (and since it will be the same reference that is going in to the particular slot in
                // the objectReferences dictionary - though potentially twice - then there is no risk of replacing a reference by accident)
                _objectReferences[referenceID.Value] = valueIfTypeIsAvailable;
            }

            // If the BinarySerialisationWriter was optimised for wide circular references then the first instance of an object definition may include an "ObjectContentPostponed"
            // flag to indicate that the instance should be created now but it will be unpopulated (for now.. it will be populated in a second pass later in). We'll track the
            // Reference IDs of deferred-initialisation / postponed-content references because we want to make sure that they all get fully initialised before the deserialisation
            // process completes
            if (nextEntryType == BinarySerialisationDataType.ObjectContentPostponed)
            {
                if (referenceID == null)
                {
                    throw new InvalidSerialisationDataFormatException(nameof(BinarySerialisationDataType.ObjectContentPostponed) + " should always appear with a ReferenceID");
                }
                nextEntryType = ReadNextDataType();
                if (nextEntryType != BinarySerialisationDataType.ObjectEnd)
                {
                    throw new InvalidSerialisationDataFormatException($"Expected {nameof(BinarySerialisationDataType.ObjectEnd)} after {nameof(BinarySerialisationDataType.ObjectContentPostponed)} but encountered {nextEntryType}");
                }
                _deferredInitialisationReferenceIDsAwaitingPopulation.Add(referenceID.Value);
                return(valueIfTypeIsAvailable);
            }
            else if (toPopulateDeferredInstance)
            {
                if (referenceID == null)
                {
                    throw new InvalidSerialisationDataFormatException($"There should always be a ReferenceID when {nameof(toPopulateDeferredInstance)} is true");
                }
                _deferredInitialisationReferenceIDsAwaitingPopulation.Remove(referenceID.Value);
            }

            var typeIfAvailable       = valueIfTypeIsAvailable?.GetType();
            var fieldsThatHaveBeenSet = new List <FieldInfo>();
            var fieldSettingInformationForGeneratingTypeBuilder = new List <BinarySerialisationReaderTypeReader.FieldSettingDetails>();

            while (true)
            {
                if (nextEntryType == BinarySerialisationDataType.ObjectEnd)
                {
                    if (valueIfTypeIsAvailable != null)
                    {
                        var fieldsThatShouldHaveBeenSet = _typeAnalyser.GetAllFieldsThatShouldBeSet(typeIfAvailable);
                        if (fieldsThatHaveBeenSet.Count != fieldsThatShouldHaveBeenSet.Length)
                        {
                            foreach (var mandatoryField in fieldsThatShouldHaveBeenSet)
                            {
                                if (fieldsThatHaveBeenSet.Find(fieldThatHasBeenSet => (fieldThatHasBeenSet.DeclaringType == mandatoryField.DeclaringType) && (fieldThatHasBeenSet.Name == mandatoryField.Name)) == null)
                                {
                                    throw new FieldNotPresentInSerialisedDataException(mandatoryField.DeclaringType.AssemblyQualifiedName, mandatoryField.Name);
                                }
                            }
                        }
                    }
                    if (typeBuilderIfAvailable != null)
                    {
                        // Create a BinarySerialisationReaderTypeReader instance for this type so that we can reuse the field name lookup results that we had to do in this
                        // loop next time (this works because the BinarySerialisationWriter will always write out the same field data in the same order for a given type,
                        // so if the data was valid this time - no missing required fields, for example - then it will be valid next time and we can read the data much
                        // mroe quickly)
                        _typeReaders[typeNameReferenceID] = new BinarySerialisationReaderTypeReader(typeBuilderIfAvailable, fieldSettingInformationForGeneratingTypeBuilder.ToArray());
                    }
                    return(valueIfTypeIsAvailable);
                }
                else if (nextEntryType == BinarySerialisationDataType.FieldName)
                {
                    nextEntryType = ReadNextDataType();
                    string rawFieldNameInformation;
                    int    fieldNameReferenceID;
                    if (nextEntryType == BinarySerialisationDataType.String)
                    {
                        rawFieldNameInformation = ReadNextString();
                        fieldNameReferenceID    = ReadNextNameReferenceID(ReadNextDataType());
                        _nameReferences[fieldNameReferenceID] = rawFieldNameInformation;
                    }
                    else
                    {
                        fieldNameReferenceID = ReadNextNameReferenceID(nextEntryType);
                        if (!_nameReferences.TryGetValue(fieldNameReferenceID, out rawFieldNameInformation))
                        {
                            throw new InvalidSerialisationDataFormatException("Invalid NameReferenceID: " + fieldNameReferenceID);
                        }
                    }
                    BinaryReaderWriterShared.SplitCombinedTypeAndFieldName(rawFieldNameInformation, out var typeNameIfRequired, out var fieldName);

                    // Try to get a reference to the field on the target type.. if there is one (if valueIfTypeIsAvailable is null then no-one cases about this data and we're just
                    // parsing it to skip over it)
                    var field = (valueIfTypeIsAvailable == null) ? null : _typeAnalyser.TryToFindField(typeIfAvailable, fieldName, typeNameIfRequired);

                    // Note: If the field doesn't exist then parse the data but don't worry about any types not being available because we're not going to set anything to the value
                    // that we get back from the "Read" call (but we still need to parse that data to advance the reader to the next field or the end of the current object)
                    var fieldValue = Read(ignoreAnyInvalidTypes: (field == null), targetTypeIfAvailable: field?.Member?.FieldType);

                    // Now that we have the value to set the field to IF IT EXISTS, try to set the field.. if it's a field that we've already identified on the type then it's easy.
                    // However, it may also have been a field on an older version of the type when it was serialised and now that it's deserialised, we'll need to check for any
                    // properties marked with [Deprecated] that we can set with the value that then set the fields that replaced the deprecated field (if this is the case then
                    // field will currently be null but valueIfTypeIsAvailable will not be null).
                    if (field != null)
                    {
                        if (field.WriterUnlessFieldShouldBeIgnored != null)
                        {
                            // The WriterUnlessFieldShouldBeIgnored has to take a "ref" argument in order to update structs but this will cause a problem if valueIfTypeIsAvailable
                            // is an object reference that isn't just the base object type (we'll need to create a reference that IS type "object" to pass in - but it will be the
                            // same reference and so it doesn't matter that we're setting the field on it indirectly)
                            if ((typeIfAvailable == CommonTypeOfs.Object) || typeIfAvailable.IsValueType)
                            {
                                field.WriterUnlessFieldShouldBeIgnored(ref valueIfTypeIsAvailable, fieldValue);
                            }
                            else
                            {
                                object refForUpdate = valueIfTypeIsAvailable;
                                field.WriterUnlessFieldShouldBeIgnored(ref refForUpdate, fieldValue);
                            }
                            fieldsThatHaveBeenSet.Add(field.Member);
                        }
                        fieldSettingInformationForGeneratingTypeBuilder.Add(new BinarySerialisationReaderTypeReader.FieldSettingDetails(
                                                                                fieldNameReferenceID,
                                                                                field.Member.FieldType,
                                                                                (field.WriterUnlessFieldShouldBeIgnored == null) ? new MemberUpdater[0] : new[] { field.WriterUnlessFieldShouldBeIgnored }
                                                                                ));
                    }
                    else if (valueIfTypeIsAvailable != null)
                    {
                        // We successfully deserialised a value but couldn't directly set a field to it - before giving up, there's something else to check.. if an older version
                        // of a type was serialised that did have the field that we've got a value for and the target type is a newer version of that type that has a [Deprecated]
                        // property that can map the old field onto a new field / property then we should try to set the [Deprecated] property's value to the value that we have.
                        // That [Deprecated] property's setter should then set a property / field on the new version of the type. If that is the case, then we can add that new
                        // property / field to the have-successfully-set list.
                        var deprecatedPropertySettingDetails = _typeAnalyser.TryToGetPropertySettersAndFieldsToConsiderToHaveBeenSet(typeIfAvailable, fieldName, typeNameIfRequired, fieldValue?.GetType());
                        if (deprecatedPropertySettingDetails != null)
                        {
                            foreach (var propertySetter in deprecatedPropertySettingDetails.PropertySetters)
                            {
                                propertySetter(ref valueIfTypeIsAvailable, fieldValue);
                            }
                            fieldsThatHaveBeenSet.AddRange(deprecatedPropertySettingDetails.RelatedFieldsThatHaveBeenSetViaTheDeprecatedProperties);
                            fieldSettingInformationForGeneratingTypeBuilder.Add(new BinarySerialisationReaderTypeReader.FieldSettingDetails(
                                                                                    fieldNameReferenceID,
                                                                                    deprecatedPropertySettingDetails.CompatibleTypeToReadAs,
                                                                                    deprecatedPropertySettingDetails.PropertySetters
                                                                                    ));
                        }
                    }
                }
                else
                {
                    throw new InvalidSerialisationDataFormatException("Unexpected data type encountered while enumerating object properties: " + nextEntryType);
                }
                nextEntryType = ReadNextDataType();
            }
        }