Skip to content

Commit

Permalink
fix(MemberChange): support multiple field store instructions
Browse files Browse the repository at this point in the history
A property setter can be written manually, without using an
auto-implemented property. In those cases users are free to set the
backing field value multiple times. This change ensures any such
field store instruction calls the change handler methods.
  • Loading branch information
Christopher - Marcel Böddecker committed Mar 20, 2019
1 parent b29c017 commit 7e504ff
Showing 1 changed file with 99 additions and 93 deletions.
192 changes: 99 additions & 93 deletions Sources/MemberChangeMethod.Fody/ModuleWeaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,133 +157,139 @@ private void InsertSetMethodCallIntoPropertySetter(
Collection<Instruction> instructions = methodBody.Instructions;

FieldReference backingField = propertyDefinition.FindBackingField();
Instruction storeInstruction = instructions.First(
IEnumerable<Instruction> storeInstructions = instructions.Where(
instruction => instruction.OpCode == OpCodes.Stfld
&& (instruction.Operand as FieldReference)?.FullName == backingField.FullName);

Instruction targetInstruction;
int instructionIndex;
bool needsPlayingCheck = _isCompilingForEditor;
bool needsActiveAndEnabledCheck =
methodReference.DeclaringType.Resolve().IsSubclassOf(_behaviourReference);

/*
if (Application.isPlaying) // Only if compiling for Editor
{
if (this.isActiveAndEnabled) // Only if in a Behaviour
foreach (Instruction storeInstruction in storeInstructions)
{
Instruction targetInstruction;
int instructionIndex;
bool needsPlayingCheck = _isCompilingForEditor;
bool needsActiveAndEnabledCheck =
methodReference.DeclaringType.Resolve().IsSubclassOf(_behaviourReference);

/*
if (Application.isPlaying) // Only if compiling for Editor
{
this.Method();
if (this.isActiveAndEnabled) // Only if in a Behaviour
{
this.Method();
}
}
}
*/

if (attribute.AttributeType.FullName == _fullBeforeChangeAttributeName)
{
targetInstruction = storeInstruction.Previous.Previous;
instructionIndex = instructions.IndexOf(targetInstruction) - 1;

Instruction testInstruction = targetInstruction.Previous;
*/

void TryFindExistingCheck(ref bool needsCheck, MemberReference checkGetterReference)
if (attribute.AttributeType.FullName == _fullBeforeChangeAttributeName)
{
if (!needsCheck)
{
return;
}
targetInstruction = storeInstruction.Previous.Previous;
instructionIndex = instructions.IndexOf(targetInstruction) - 1;

Instruction testInstruction = targetInstruction.Previous;

while (testInstruction != null)
void TryFindExistingCheck(ref bool needsCheck, MemberReference checkGetterReference)
{
if (testInstruction.OpCode == OpCodes.Call
&& (testInstruction.Operand as MethodReference)?.FullName == checkGetterReference.FullName)
if (!needsCheck)
{
needsCheck = false;
return;
}

testInstruction = testInstruction.Previous;
}
while (testInstruction != null)
{
if (testInstruction.OpCode == OpCodes.Call
&& (testInstruction.Operand as MethodReference)?.FullName
== checkGetterReference.FullName)
{
needsCheck = false;
return;
}

testInstruction = testInstruction.Previous;
}

while (testInstruction != null
&& (testInstruction.OpCode == OpCodes.Brfalse || testInstruction.OpCode == OpCodes.Brfalse_S))
{
testInstruction = testInstruction.Next;
}
while (testInstruction != null
&& (testInstruction.OpCode == OpCodes.Brfalse
|| testInstruction.OpCode == OpCodes.Brfalse_S))
{
testInstruction = testInstruction.Next;
}

if (testInstruction != null)
{
instructionIndex = instructions.IndexOf(testInstruction) - 1;
if (testInstruction != null)
{
instructionIndex = instructions.IndexOf(testInstruction) - 1;
}
}
}

TryFindExistingCheck(ref needsActiveAndEnabledCheck, _isActiveAndEnabledGetterReference);
TryFindExistingCheck(ref needsPlayingCheck, _isApplicationPlayingGetterReference);
}
else
{
targetInstruction = storeInstruction.Next;
instructionIndex = instructions.IndexOf(targetInstruction) - 1;

Instruction testInstruction = storeInstruction.Next;

void TryFindExistingCheck(ref bool needsCheck, MemberReference checkGetterReference)
TryFindExistingCheck(ref needsActiveAndEnabledCheck, _isActiveAndEnabledGetterReference);
TryFindExistingCheck(ref needsPlayingCheck, _isApplicationPlayingGetterReference);
}
else
{
if (!needsCheck)
{
return;
}
targetInstruction = storeInstruction.Next;
instructionIndex = instructions.IndexOf(targetInstruction) - 1;

while (testInstruction != null)
Instruction testInstruction = storeInstruction.Next;

void TryFindExistingCheck(ref bool needsCheck, MemberReference checkGetterReference)
{
if (testInstruction.OpCode == OpCodes.Call
&& (testInstruction.Operand as MethodReference)?.FullName == checkGetterReference.FullName)
if (!needsCheck)
{
needsCheck = false;
instructionIndex = instructions.IndexOf(testInstruction) + 1;
return;
}

testInstruction = testInstruction.Next;
while (testInstruction != null)
{
if (testInstruction.OpCode == OpCodes.Call
&& (testInstruction.Operand as MethodReference)?.FullName
== checkGetterReference.FullName)
{
needsCheck = false;
instructionIndex = instructions.IndexOf(testInstruction) + 1;
return;
}

testInstruction = testInstruction.Next;
}
}

TryFindExistingCheck(ref needsPlayingCheck, _isApplicationPlayingGetterReference);
TryFindExistingCheck(ref needsActiveAndEnabledCheck, _isActiveAndEnabledGetterReference);
}

TryFindExistingCheck(ref needsPlayingCheck, _isApplicationPlayingGetterReference);
TryFindExistingCheck(ref needsActiveAndEnabledCheck, _isActiveAndEnabledGetterReference);
}
if (needsPlayingCheck)
{
// Call Application.isPlaying getter
instructions.Insert(
++instructionIndex,
Instruction.Create(OpCodes.Call, _isApplicationPlayingGetterReference));
// Bail out if false
instructions.Insert(++instructionIndex, Instruction.Create(OpCodes.Brfalse, targetInstruction));
}

if (needsPlayingCheck)
{
// Call Application.isPlaying getter
instructions.Insert(
++instructionIndex,
Instruction.Create(OpCodes.Call, _isApplicationPlayingGetterReference));
// Bail out if false
instructions.Insert(++instructionIndex, Instruction.Create(OpCodes.Brfalse, targetInstruction));
}
if (needsActiveAndEnabledCheck)
{
// Load this (for getter call)
instructions.Insert(++instructionIndex, Instruction.Create(OpCodes.Ldarg_0));
// Call Behaviour.isActiveAndEnabled getter
instructions.Insert(
++instructionIndex,
Instruction.Create(OpCodes.Call, _isActiveAndEnabledGetterReference));
// Bail out if false
instructions.Insert(++instructionIndex, Instruction.Create(OpCodes.Brfalse, targetInstruction));
}

if (needsActiveAndEnabledCheck)
{
// Load this (for getter call)
// Load this (for method call)
instructions.Insert(++instructionIndex, Instruction.Create(OpCodes.Ldarg_0));
// Call Behaviour.isActiveAndEnabled getter
// Call method
instructions.Insert(
++instructionIndex,
Instruction.Create(OpCodes.Call, _isActiveAndEnabledGetterReference));
// Bail out if false
instructions.Insert(++instructionIndex, Instruction.Create(OpCodes.Brfalse, targetInstruction));
}

// Load this (for method call)
instructions.Insert(++instructionIndex, Instruction.Create(OpCodes.Ldarg_0));
// Call method
instructions.Insert(
++instructionIndex,
Instruction.Create(OpCodes.Callvirt, methodReference.CreateGenericMethodIfNeeded()));
Instruction.Create(OpCodes.Callvirt, methodReference.CreateGenericMethodIfNeeded()));

methodBody.OptimizeMacros();
methodBody.OptimizeMacros();

LogInfo(
$"Inserted a call to the method '{methodReference.FullName}' into"
+ $" the setter of the property '{propertyDefinition.FullName}'.");
LogInfo(
$"Inserted a call to the method '{methodReference.FullName}' into"
+ $" the setter of the property '{propertyDefinition.FullName}'.");
}
}
}
}

0 comments on commit 7e504ff

Please sign in to comment.