-
Notifications
You must be signed in to change notification settings - Fork 173
Remove backing fields when weaving generated classes #3088
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
Changes from all commits
515cd3d
79d6e58
58f9e1f
71bd2e4
e641469
aaf2f99
3ab1482
61bf95f
283172b
28d1ada
763fee7
cccdf49
99dec2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -247,6 +247,50 @@ Analytics payload | |
| return WeaveModuleResult.Success(weaveResults); | ||
| } | ||
|
|
||
| private static void RemoveBackingFields(TypeDefinition type, HashSet<MetadataToken> backingFields) | ||
| { | ||
| for (var i = type.Fields.Count - 1; i >= 0; i--) | ||
| { | ||
| var field = type.Fields[i]; | ||
| if (backingFields.Contains(field.MetadataToken)) | ||
| { | ||
| type.Fields.RemoveAt(i); | ||
| } | ||
| } | ||
|
|
||
| // Iterates through all constructors' instructions from the end to start. | ||
| foreach (var constructor in type.GetConstructors()) | ||
| { | ||
| // Index of the most recent "Stfld <backing_field>" instruction | ||
| var backingFieldInstructionsEnd = -1; | ||
| for (var i = constructor.Body.Instructions.Count - 1; i >= 0; i--) | ||
| { | ||
| var instruction = constructor.Body.Instructions[i]; | ||
|
|
||
| // If it comes across "Stfld <backing_field>" | ||
| // it considers this the end index of backing field initializaion instructions. | ||
| if (instruction.OpCode == OpCodes.Stfld && instruction.Operand is FieldReference field) | ||
| { | ||
| if (backingFields.Contains(field.MetadataToken)) | ||
| { | ||
| backingFieldInstructionsEnd = i; | ||
| } | ||
| } | ||
|
|
||
| // If it comes across "Ldarg 0", | ||
| // it considers this the start index of backing field initializaion instructions | ||
| // and removes all backing field instructions from end to start. | ||
| else if (instruction.OpCode == OpCodes.Ldarg_0) | ||
| { | ||
| for (var j = backingFieldInstructionsEnd; j >= i; j--) | ||
| { | ||
| constructor.Body.Instructions.RemoveAt(j); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private WeaveTypeResult WeaveGeneratedType(TypeDefinition type) | ||
| { | ||
| _logger.Debug("Weaving generated " + type.Name); | ||
|
|
@@ -256,14 +300,21 @@ private WeaveTypeResult WeaveGeneratedType(TypeDefinition type) | |
| var interfaceType = _moduleDefinition.GetType($"{@namespace}.Generated", interfaceName); | ||
|
|
||
| var persistedProperties = new List<WeavePropertyResult>(); | ||
| var backingFields = new HashSet<MetadataToken>(); | ||
|
|
||
| // We need to weave all (and only) the properties in the accessor interface | ||
| foreach (var interfaceProperty in interfaceType.Properties) | ||
| { | ||
| var prop = type.Properties.First(p => p.Name == interfaceProperty.Name); | ||
|
|
||
| try | ||
| { | ||
| // Stash and remove the backing field before weaving as it depends on get method. | ||
| var backingField = prop.GetBackingField(); | ||
| if (backingField != null) | ||
| { | ||
| backingFields.Add(backingField.MetadataToken); | ||
| } | ||
|
|
||
| var weaveResult = WeaveGeneratedClassProperty(type, prop, interfaceType); | ||
| persistedProperties.Add(weaveResult); | ||
| } | ||
|
|
@@ -278,6 +329,8 @@ private WeaveTypeResult WeaveGeneratedType(TypeDefinition type) | |
| } | ||
| } | ||
|
|
||
| RemoveBackingFields(type, backingFields); | ||
|
|
||
| return WeaveTypeResult.Success(type.Name, persistedProperties, isGenerated: true); | ||
| } | ||
|
|
||
|
|
@@ -310,15 +363,16 @@ private static void ReplaceGeneratedClassGetter(PropertyDefinition prop, TypeDef | |
| //// This is equivalent to: | ||
| //// get => Accessor.Property; | ||
|
|
||
| var start = prop.GetMethod.Body.Instructions.First(); | ||
| var il = prop.GetMethod.Body.GetILProcessor(); | ||
| prop.GetMethod.Body.Instructions.Clear(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the previous implementation of replacing the getter was causing some unintended behavior, it was actually just adding instructions to the getter rather than removing the old instructions---the reason why this was not noticeable was because the old instruction became unreachable because of the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good eye! Need to take a look at this |
||
| prop.GetMethod.Body.Variables.Clear(); | ||
|
|
||
| var propertyGetterOnAccessorReference = new MethodReference($"get_{prop.Name}", prop.PropertyType, interfaceType) { HasThis = true }; | ||
|
|
||
| il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); | ||
| il.InsertBefore(start, il.Create(OpCodes.Call, accessorGetter)); | ||
| il.InsertBefore(start, il.Create(OpCodes.Callvirt, propertyGetterOnAccessorReference)); | ||
| il.InsertBefore(start, il.Create(OpCodes.Ret)); | ||
| il.Append(il.Create(OpCodes.Ldarg_0)); | ||
| il.Append(il.Create(OpCodes.Call, accessorGetter)); | ||
| il.Append(il.Create(OpCodes.Callvirt, propertyGetterOnAccessorReference)); | ||
| il.Append(il.Create(OpCodes.Ret)); | ||
| } | ||
|
|
||
| private void ReplaceGeneratedClassSetter(PropertyDefinition prop, TypeDefinition interfaceType, MethodReference accessorGetter) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| //////////////////////////////////////////////////////////////////////////// | ||
| // | ||
| // Copyright 2022 Realm Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
| // | ||
| //////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| namespace Realms.Tests.Database | ||
gagik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| public static class Generator | ||
| { | ||
| private static int _currentId; | ||
|
|
||
| public static int GetId() => _currentId++; | ||
| } | ||
|
|
||
| public partial class InitializedFieldObject : Realms.IRealmObject | ||
| { | ||
| public int Id { get; set; } = Generator.GetId(); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.