private void PopulatePropertyWithGeneratedDefaultValue(ISarifNode node, PropertyInfo property) { // If we can't set this property, it is not of interest if (property.GetAccessors().Length != 2) { return; } // This special-casing is required to account for the fact that an // exception instance itself contains an array of exceptions // (exception.innerExceptions). We don't want to hydrate // the innerExceptions of any innerExceptions, which results // in re-entrancy that ends up consuming all stack space. // // Once we have populated a single exceptions.innerExceptions array, // we have accomplished all the testing we need in any case. if (node.SarifNodeKind == SarifNodeKind.ExceptionData && property.Name == "InnerExceptions" && _visitingExceptionData) { return; } // Similar approach applies for graph node.children if (node.SarifNodeKind == SarifNodeKind.Node && property.Name == "Children" && _visitingGraphNode) { return; } object propertyValue = null; Type propertyType = property.PropertyType; // isRequired flag ensures we don't end up generating a SARIF file that's missing a required property or an anyOf required property, // because such a file wouldn't validate. bool isRequired = PropertyIsRequiredBySchema(node.GetType().Name, property.Name) || PropertyIsAnyOfRequiredBySchema(node.GetType().Name, property.Name); if (GetPropertyFormatPattern(node.GetType().Name, property.Name) is string propertyFormatPattern) { propertyValue = GetFormattedStringValue(propertyFormatPattern); } else if (_typeToPropertyValueConstructorMap.TryGetValue(propertyType, out PrimitiveValueBuilder propertyValueBuilder)) { propertyValue = propertyValueBuilder(isRequired); } else if (HasParameterlessConstructor(propertyType)) { propertyValue = Activator.CreateInstance(propertyType); } else if (IsList(propertyType)) { propertyValue = CreateEmptyList(propertyType); Type genericTypeArgument = propertyType.GenericTypeArguments[0]; object listElement = null; // For arrays that are populated with SARIF types, we will instantiate a // single object instance and put it into the array. This allows the // default populating visitor to continue to explore the object model and // exercise nested types. This approach prevents comprehensive testing of // the arrays populated in this way (because they are non-empty if (genericTypeArgument.FullName.StartsWith("Microsoft.CodeAnalysis.Sarif.")) { listElement = Activator.CreateInstance(propertyType.GenericTypeArguments[0]); } else if (_typeToPropertyValueConstructorMap.TryGetValue(genericTypeArgument, out propertyValueBuilder)) { listElement = propertyValueBuilder(isRequired); } AddElementToList(propertyValue, listElement); } else if (IsDictionary(propertyType)) { Type genericTypeArgument = propertyType.GenericTypeArguments[1]; // We do not populate any propert bags directly. Instead, we will use the // IPropertyBagHolder API to instantiate and then empty these constructs if (genericTypeArgument != typeof(SerializedPropertyInfo)) { propertyValue = CreateEmptyDictionary(propertyType); // For dictionaries that are populated with SARIF types, we will instantiate a // single object instance and store it using an arbitrary key. This approach // ensures we populate the SARIF sample with all possible object types if (genericTypeArgument.FullName.StartsWith("Microsoft.CodeAnalysis.Sarif.")) { object dictionaryValue = Activator.CreateInstance(genericTypeArgument); AddElementToDictionary(propertyValue, dictionaryValue); } } } else if ((property.PropertyType.BaseType == typeof(Enum))) { // This code sets any enum to the first non-zero value we encounter foreach (var enumValue in Enum.GetValues(property.PropertyType)) { if ((int)enumValue != 0) { propertyValue = enumValue; break; } } // This code ensures both that we encounter an enum value that is non-zero, // and that no enum definitions skips the value of one in its definition if ((int)propertyValue != 1) { throw new InvalidOperationException(); } } property.SetValue(node, propertyValue); }
private void PopulateInstanceWithDefaultMemberValues(ISarifNode node) { Type nodeType = node.GetType(); var binding = BindingFlags.Public | BindingFlags.Instance; foreach (PropertyInfo property in nodeType.GetProperties(binding)) { // The node kind is always properly set in the OM and // isn't relevant to the SARIF schema itself if (property.Name == "SarifNodeKind") { continue; } // Property bags and tags are populated via special methods on // the class rather than direct access of properties. These // property names extend from the PropertyBagHolder base type // that all properties-bearing types extend (nearly every SARIF // class at this point). if (property.Name == "PropertyNames" || property.Name == "Tags") { continue; } if (ShouldExcludePopulationDueToOneOfCriteria(node.GetType().Name, property.Name)) { continue; } object defaultValue = null; MemberInfo member = nodeType.GetMember(property.Name)[0]; foreach (CustomAttributeData attributeData in member?.GetCustomAttributesData()) { if (attributeData.AttributeType.FullName != "System.ComponentModel.DefaultValueAttribute") { continue; } defaultValue = attributeData.ConstructorArguments[0].Value; // DefaultValue of -1 is to ensure an actual value of 0 will not be ignored during serialization, // Hence, we will substitute them with 0. if (defaultValue is int defaultIntValue && defaultIntValue == -1) { defaultValue = 0; } } // If the member is decorated with a default value, we'll inject it. Otherwise, // we'll compute a default value based on the node type if (defaultValue != null) { property.SetValue(node, defaultValue); continue; } PopulatePropertyWithGeneratedDefaultValue(node, property); } // If we have a property bag holder (as most SARIF things are), we will // add and then immediately remove both a property and a tag. This has // the effect of leaving non-null but empty properties collection. var propertyBagHolder = node as PropertyBagHolder; if (propertyBagHolder != null) { string meaninglessValue = "ToBeRemoved"; propertyBagHolder.SetProperty(propertyName: meaninglessValue, value: meaninglessValue); propertyBagHolder.RemoveProperty(propertyName: meaninglessValue); propertyBagHolder.Tags.Add(meaninglessValue); propertyBagHolder.Tags.Remove(meaninglessValue); } }