public void VisitNode (JSInvocationExpression invocation) { FunctionAnalysis2ndPass sa = null; if (invocation.JSMethod != null) sa = GetSecondPass(invocation.JSMethod); CloneArgumentsIfNecessary(invocation.Parameters, invocation.Arguments, sa); var thisReference = invocation.ThisReference; var thisReferenceType = thisReference.GetActualType(TypeSystem); var thisReferenceIsStruct = TypeUtil.IsStruct(thisReferenceType) && !TypeUtil.IsNullable(thisReferenceType); bool thisReferenceNeedsCopy = false; bool thisReferenceNeedsCopyAndReassignment = false; if (thisReferenceIsStruct && !invocation.SuppressThisClone) { var isMethodInvocation = (thisReference != null) && (sa != null) && !(ParentNode is JSCommaExpression) && !(thisReference is JSStructCopyExpression) && !( (ParentNode is JSResultReferenceExpression) && Stack.OfType<JSCommaExpression>().Any() ); // If a struct is immutable, a method may reassign the this-reference, // i.e. 'this = otherstruct' successfully. // In this scenario we have to clone the old this-reference, invoke // the method on the clone, and replace the old this-reference with // the new, modified clone. thisReferenceNeedsCopyAndReassignment = isMethodInvocation && sa.ViolatesThisReferenceImmutability; // When invoking a method that mutates a struct's members or lets them escape, // we need to copy the this-reference if it isn't writable. // FIXME: We're white-listing writable targets, but we probably want to blacklist // non-writable targets instead, so that if a fn's result is new we don't clone it // to use it as a this-reference. // FIXME: Handle pointers, replace x.get().foo() with some sort of comma expr, // like ($x = x.get(), $x.foo(), x.set($x)) ? var isWritableInstance = (thisReference is JSFieldAccess) || (thisReference is JSVariable) || (thisReference is JSReadThroughReferenceExpression); thisReferenceNeedsCopy = isMethodInvocation && ( sa.IsVariableModified("this") || // Maybe don't include return here? sa.DoesVariableEscape("this", true) ) && (!isWritableInstance); } GenericParameter relevantParameter; var isCopyNeeded = IsCopyNeeded(thisReference, out relevantParameter, false); if ( thisReferenceNeedsCopyAndReassignment && isCopyNeeded ) { if (TraceInsertedCopies) Console.WriteLine("Cloning this-reference because method reassigns this: {0}", invocation); if ( (thisReference is JSFieldAccess) || (thisReference is JSVariable) ) { var rre = ParentNode as JSResultReferenceExpression; var cloneExpr = new JSBinaryOperatorExpression( JSOperator.Assignment, thisReference, MakeCopyForExpression(thisReference, relevantParameter), thisReferenceType ); var commaExpression = new JSCommaExpression( cloneExpr, (rre != null) ? (JSExpression)new JSResultReferenceExpression(invocation) : (JSExpression)invocation ); if (rre != null) { ResultReferenceReplacement = commaExpression; return; } else { ParentNode.ReplaceChild(invocation, commaExpression); VisitReplacement(commaExpression); return; } } else { // Huh? invocation.ReplaceChild(thisReference, MakeCopyForExpression(thisReference, relevantParameter)); VisitChildren(invocation); return; } } else if (thisReferenceNeedsCopy && isCopyNeeded) { if (TraceInsertedCopies) Console.WriteLine("Cloning this-reference because method mutates this and this-reference is not field/local: {0}", invocation); invocation.ReplaceChild(thisReference, MakeCopyForExpression(thisReference, relevantParameter)); VisitChildren(invocation); return; } VisitChildren(invocation); }
public void VisitNode(JSInvocationExpression invocation) { FunctionAnalysis2ndPass sa = null; if (invocation.JSMethod != null) { sa = GetSecondPass(invocation.JSMethod); } CloneArgumentsIfNecessary(invocation.Parameters, invocation.Arguments, sa); var thisReference = invocation.ThisReference; var thisReferenceType = thisReference.GetActualType(TypeSystem); var thisReferenceIsStruct = TypeUtil.IsStruct(thisReferenceType) && !TypeUtil.IsNullable(thisReferenceType); bool thisReferenceNeedsCopy = false; bool thisReferenceNeedsCopyAndReassignment = false; if (thisReferenceIsStruct && !invocation.SuppressThisClone) { var isMethodInvocation = (thisReference != null) && (sa != null) && !(ParentNode is JSCommaExpression) && !(thisReference is JSStructCopyExpression) && !( (ParentNode is JSResultReferenceExpression) && Stack.OfType <JSCommaExpression>().Any() ); // If a struct is immutable, a method may reassign the this-reference, // i.e. 'this = otherstruct' successfully. // In this scenario we have to clone the old this-reference, invoke // the method on the clone, and replace the old this-reference with // the new, modified clone. thisReferenceNeedsCopyAndReassignment = isMethodInvocation && sa.ViolatesThisReferenceImmutability; // When invoking a method that mutates a struct's members or lets them escape, // we need to copy the this-reference if it isn't writable. // FIXME: We're white-listing writable targets, but we probably want to blacklist // non-writable targets instead, so that if a fn's result is new we don't clone it // to use it as a this-reference. // FIXME: Handle pointers, replace x.get().foo() with some sort of comma expr, // like ($x = x.get(), $x.foo(), x.set($x)) ? var isWritableInstance = (thisReference is JSFieldAccess) || (thisReference is JSVariable) || (thisReference is JSReadThroughReferenceExpression); thisReferenceNeedsCopy = isMethodInvocation && ( sa.IsVariableModified("this") || // Maybe don't include return here? sa.DoesVariableEscape("this", true) ) && (!isWritableInstance); } GenericParameter relevantParameter; var isCopyNeeded = IsCopyNeeded(thisReference, out relevantParameter, false); if ( thisReferenceNeedsCopyAndReassignment && isCopyNeeded ) { if (TraceInsertedCopies) { Console.WriteLine("Cloning this-reference because method reassigns this: {0}", invocation); } if ( (thisReference is JSFieldAccess) || (thisReference is JSVariable) ) { var rre = ParentNode as JSResultReferenceExpression; var cloneExpr = new JSBinaryOperatorExpression( JSOperator.Assignment, thisReference, MakeCopyForExpression(thisReference, relevantParameter), thisReferenceType ); var commaExpression = new JSCommaExpression( cloneExpr, (rre != null) ? (JSExpression) new JSResultReferenceExpression(invocation) : (JSExpression)invocation ); if (rre != null) { ResultReferenceReplacement = commaExpression; return; } else { ParentNode.ReplaceChild(invocation, commaExpression); VisitReplacement(commaExpression); return; } } else { // Huh? invocation.ReplaceChild(thisReference, MakeCopyForExpression(thisReference, relevantParameter)); VisitChildren(invocation); return; } } else if (thisReferenceNeedsCopy && isCopyNeeded) { if (TraceInsertedCopies) { Console.WriteLine("Cloning this-reference because method mutates this and this-reference is not field/local: {0}", invocation); } invocation.ReplaceChild(thisReference, MakeCopyForExpression(thisReference, relevantParameter)); VisitChildren(invocation); return; } VisitChildren(invocation); }
public void VisitNode(JSInvocationExpression invocation) { FunctionAnalysis2ndPass sa = null; if (invocation.JSMethod != null) { sa = GetSecondPass(invocation.JSMethod); } CloneArgumentsIfNecessary(invocation.Parameters, invocation.Arguments, sa); var thisReference = invocation.ThisReference; if ( (thisReference != null) && (sa != null) && !(ParentNode is JSCommaExpression) && !(thisReference is JSStructCopyExpression) && !( (ParentNode is JSResultReferenceExpression) && Stack.OfType <JSCommaExpression>().Any() ) && ( sa.ViolatesThisReferenceImmutability || sa.ModifiedVariables.Contains("this") || sa.EscapingVariables.Contains("this") ) ) { // The method we're calling violates immutability so we need to clone the this-reference // before we call it. var thisReferenceType = thisReference.GetActualType(TypeSystem); if (TypeUtil.IsStruct(thisReferenceType) && !TypeUtil.IsStructImmutable(thisReferenceType)) { if ((thisReference is JSVariable) || (thisReference is JSFieldAccess)) { var rre = ParentNode as JSResultReferenceExpression; var cloneExpr = new JSBinaryOperatorExpression( JSOperator.Assignment, thisReference, new JSStructCopyExpression(thisReference), thisReferenceType ); var commaExpression = new JSCommaExpression( cloneExpr, (rre != null) ? (JSExpression) new JSResultReferenceExpression(invocation) : (JSExpression)invocation ); if (rre != null) { ResultReferenceReplacement = commaExpression; } else { ParentNode.ReplaceChild(invocation, commaExpression); VisitReplacement(commaExpression); } } else { invocation.ReplaceChild(thisReference, new JSStructCopyExpression(thisReference)); VisitChildren(invocation); } return; } } VisitChildren(invocation); }