Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions src/core/IronPython/Runtime/FunctionCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,19 +584,21 @@ internal object Call(CodeContext/*!*/ context) {
throw PythonOps.TypeError("cannot exec code object that contains free variables: {0}", co_freevars.__repr__(context));
}

if (Target == null || (Target.GetMethodInfo()?.DeclaringType == typeof(PythonCallTargets))) {
UpdateDelegate(context.LanguageContext, true);
// Target is read only once, since switching tracing on or off on another thread may change it at any time.
Delegate target = Target;
if (target == null || (target.GetMethodInfo()?.DeclaringType == typeof(PythonCallTargets))) {
target = UpdateDelegate(context.LanguageContext, true);
}

if (Target is Func<CodeContext, CodeContext> classTarget) {
if (target is Func<CodeContext, CodeContext> classTarget) {
return classTarget(context);
}

if (Target is LookupCompilationDelegate moduleCode) {
if (target is LookupCompilationDelegate moduleCode) {
return moduleCode(context, this);
}

if (Target is Func<FunctionCode, object> optimizedModuleCode) {
if (target is Func<FunctionCode, object> optimizedModuleCode) {
return optimizedModuleCode(this);
}

Expand Down Expand Up @@ -678,21 +680,23 @@ internal void LazyCompileFirstTarget(PythonFunction function) {
}

/// <summary>
/// Updates the delegate based upon current Python context settings for recursion enforcement
/// and for tracing.
/// Updates the delegate based upon current Python context settings for recursion enforcement and for tracing.
/// </summary>
internal void UpdateDelegate(PythonContext context, bool forceCreation) {
/// <returns>
/// The delegate set as the target, or null if only a lazy target was set (only when <paramref name="forceCreation"/> is false).
/// A caller that needs the delegate should use this result rather than read <see cref="Target"/> again: another thread may change
/// it at any time, e.g. by switching tracing on or off.
/// </returns>
internal Delegate UpdateDelegate(PythonContext context, bool forceCreation) {
Delegate finalTarget;

if (context.EnableTracing && _lambda != null) {
if (_tracingLambda == null) {
if (!forceCreation) {
if (_lambda is Compiler.Ast.ClassDefinition) return; // ClassDefinition does not have the same signature as call targets
// the user just called sys.settrace(), don't force re-compilation of every method in the system. Instead
// we'll just re-compile them as they're invoked.
PythonCallTargets.GetPythonTargetType(_lambda.ParameterNames.Length > PythonCallTargets.MaxArgs, _lambda.ParameterNames.Length, out Delegate target);
SetTarget(target);
return;
SetLazyTarget();
return null;
}
_tracingLambda = GetGeneratorOrNormalLambdaTracing(context);
}
Expand All @@ -708,9 +712,8 @@ internal void UpdateDelegate(PythonContext context, bool forceCreation) {
// we cannot create the delegate when forceCreation is false because we hold the
// _CodeCreateAndUpdateDelegateLock and compiling the delegate may create a FunctionCode
// object which requires the lock.
PythonCallTargets.GetPythonTargetType(_lambda.ParameterNames.Length > PythonCallTargets.MaxArgs, _lambda.ParameterNames.Length, out Delegate target);
SetTarget(target);
return;
SetLazyTarget();
return null;
}
_normalDelegate = CompileLambda(GetGeneratorOrNormalLambda(), new TargetUpdaterForCompilation(context, this).SetCompiledTarget);
}
Expand All @@ -721,6 +724,24 @@ internal void UpdateDelegate(PythonContext context, bool forceCreation) {
finalTarget = AddRecursionCheck(context, finalTarget);

SetTarget(finalTarget);
return finalTarget;
}

/// <summary>
/// Sets a target that creates the delegate for the current mode (tracing or not) when it is first needed.
/// </summary>
/// <remarks>
/// A function gets a call target that compiles its delegate on the first call. A class body cannot get one, since its
/// delegate has a different signature (<c>Func&lt;CodeContext, CodeContext&gt;</c>); it gets a null target instead, as when it
/// has not been compiled yet, which makes <see cref="PythonOps.MakeClass"/> compile it the next time the class is created.
/// </remarks>
private void SetLazyTarget() {
if (_lambda is Compiler.Ast.ClassDefinition) {
SetTarget(null);
} else {
PythonCallTargets.GetPythonTargetType(_lambda.ParameterNames.Length > PythonCallTargets.MaxArgs, _lambda.ParameterNames.Length, out Delegate target);
SetTarget(target);
}
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions src/core/IronPython/Runtime/Operations/PythonOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1589,10 +1589,10 @@ public static void InitializeForFinalization(CodeContext/*!*/ context, object ne

static Func<CodeContext, CodeContext> GetClassCode(CodeContext/*!*/ context, FunctionCode funcCode, Func<CodeContext, CodeContext> body) {
if (body == null) {
if (funcCode.Target == null) {
funcCode.UpdateDelegate(context.LanguageContext, true);
}
return (Func<CodeContext, CodeContext>)funcCode.Target!;
// Target is read only once, since switching tracing on or off on another thread may reset it to null at any time
// (see FunctionCode.UpdateDelegate). If it is null, compile and use the delegate that compilation returns.
var target = (Func<CodeContext, CodeContext>?)funcCode.Target;
return target ?? (Func<CodeContext, CodeContext>)funcCode.UpdateDelegate(context.LanguageContext, true);
} else {
if (funcCode.Target == null) {
funcCode.SetTarget(body);
Expand Down
15 changes: 15 additions & 0 deletions tests/suite/modules/system_related/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,21 @@ def g():

sys.settrace(None)

def test_class_body_after_settrace_none(self):
"""A class body that has only run traced still runs after sys.settrace(None)."""
def f():
class Boom(Exception): pass
return Boom

sys.settrace(lambda *args: None)
try:
f()
finally:
sys.settrace(None)
# Before the fix, this failed (intermittently) with a TypeError about a Func`2[PythonFunction,Object] cast:
# sys.settrace(None) gave the class body a function's call target.
self.assertTrue(issubclass(f(), Exception))

@skipUnlessIronPython()
@unittest.expectedFailure # dummy getrefcount removed in https://github.com/IronLanguages/ironpython3/pull/577
def test_getrefcount(self):
Expand Down