11/*
2- * Copyright (c) 2018, 2025 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2018, 2026 , Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * The Universal Permissive License (UPL), Version 1.0
4343import static com .oracle .graal .python .builtins .objects .thread .AbstractPythonLock .TIMEOUT_MAX ;
4444import static com .oracle .graal .python .nodes .BuiltinNames .J_EXIT ;
4545import static com .oracle .graal .python .nodes .BuiltinNames .J__THREAD ;
46+ import static com .oracle .graal .python .nodes .BuiltinNames .T_STDERR ;
47+ import static com .oracle .graal .python .nodes .BuiltinNames .T___EXCEPTHOOK__ ;
4648import static com .oracle .graal .python .nodes .BuiltinNames .T__THREAD ;
4749import static com .oracle .graal .python .util .PythonUtils .tsLiteral ;
4850
5759import com .oracle .graal .python .builtins .PythonBuiltinClassType ;
5860import com .oracle .graal .python .builtins .PythonBuiltins ;
5961import com .oracle .graal .python .builtins .objects .PNone ;
62+ import com .oracle .graal .python .builtins .objects .common .SequenceStorageNodes ;
63+ import com .oracle .graal .python .builtins .objects .exception .PBaseException ;
6064import com .oracle .graal .python .builtins .objects .function .PKeyword ;
6165import com .oracle .graal .python .builtins .objects .module .PythonModule ;
66+ import com .oracle .graal .python .builtins .objects .object .PythonObject ;
6267import com .oracle .graal .python .builtins .objects .thread .PLock ;
6368import com .oracle .graal .python .builtins .objects .thread .PThread ;
69+ import com .oracle .graal .python .builtins .objects .tuple .PTuple ;
70+ import com .oracle .graal .python .builtins .objects .tuple .StructSequence ;
71+ import com .oracle .graal .python .builtins .objects .type .TypeNodes ;
6472import com .oracle .graal .python .lib .PyNumberAsSizeNode ;
73+ import com .oracle .graal .python .lib .PyObjectLookupAttr ;
74+ import com .oracle .graal .python .lib .PyObjectSetAttr ;
75+ import com .oracle .graal .python .lib .PyObjectStrAsTruffleStringNode ;
6576import com .oracle .graal .python .nodes .ErrorMessages ;
6677import com .oracle .graal .python .nodes .PRaiseNode ;
6778import com .oracle .graal .python .nodes .WriteUnraisableNode ;
7687import com .oracle .graal .python .nodes .function .builtins .PythonUnaryClinicBuiltinNode ;
7788import com .oracle .graal .python .nodes .function .builtins .clinic .ArgumentClinicProvider ;
7889import com .oracle .graal .python .nodes .object .BuiltinClassProfiles .IsBuiltinObjectProfile ;
90+ import com .oracle .graal .python .nodes .object .GetClassNode ;
7991import com .oracle .graal .python .runtime .GilNode ;
8092import com .oracle .graal .python .runtime .PythonContext ;
8193import com .oracle .graal .python .runtime .exception .PException ;
8294import com .oracle .graal .python .runtime .exception .PythonThreadKillException ;
8395import com .oracle .graal .python .runtime .object .PFactory ;
96+ import com .oracle .graal .python .runtime .sequence .storage .SequenceStorage ;
8497import com .oracle .truffle .api .CompilerDirectives .TruffleBoundary ;
8598import com .oracle .truffle .api .TruffleLanguage ;
8699import com .oracle .truffle .api .TruffleThreadBuilder ;
97110@ CoreFunctions (defineModule = J__THREAD )
98111public final class ThreadModuleBuiltins extends PythonBuiltins {
99112
113+ public static final StructSequence .BuiltinTypeDescriptor EXCEPTHOOK_ARGS_DESC = new StructSequence .BuiltinTypeDescriptor (
114+ PythonBuiltinClassType .PExceptHookArgs ,
115+ 4 ,
116+ new String []{
117+ "exc_type" , "exc_value" , "exc_traceback" , "thread" },
118+ new String []{
119+ "Exception type" , "Exception value" , "Exception traceback" ,
120+ "Exception thread" });
121+
100122 @ Override
101123 protected List <? extends NodeFactory <? extends PythonBuiltinBaseNode >> getNodeFactories () {
102124 return ThreadModuleBuiltinsFactory .getFactories ();
@@ -106,6 +128,7 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
106128 public void initialize (Python3Core core ) {
107129 addBuiltinConstant ("error" , core .lookupType (PythonBuiltinClassType .RuntimeError ));
108130 addBuiltinConstant ("TIMEOUT_MAX" , TIMEOUT_MAX );
131+ StructSequence .initType (core , EXCEPTHOOK_ARGS_DESC );
109132 core .lookupBuiltinModule (T__THREAD ).setModuleState (0 );
110133 super .initialize (core );
111134 }
@@ -173,6 +196,92 @@ static long getStackSize(VirtualFrame frame, Object stackSizeObj,
173196 }
174197 }
175198
199+ @ Builtin (name = "_excepthook" , minNumOfPositionalArgs = 2 , declaresExplicitSelf = true )
200+ @ GenerateNodeFactory
201+ abstract static class GetThreadExceptHookNode extends PythonBinaryBuiltinNode {
202+ @ Specialization
203+ Object getExceptHook (@ SuppressWarnings ("unused" ) PythonModule self ,
204+ Object exceptHookArgs ,
205+ @ Bind Node inliningTarget ,
206+ @ Cached PRaiseNode raiseNode ,
207+ @ Cached CallNode callNode ,
208+ @ Cached PyObjectLookupAttr lookupAttr ,
209+ @ Cached PyObjectSetAttr setAttr ,
210+ @ Cached PyObjectStrAsTruffleStringNode strNode ) {
211+
212+ Object argsType = GetClassNode .GetPythonObjectClassNode .executeUncached ((PythonObject ) exceptHookArgs );
213+ if (!TypeNodes .IsSameTypeNode .executeUncached (argsType , PythonBuiltinClassType .PExceptHookArgs )) {
214+ throw PRaiseNode .getUncached ().raise (raiseNode , PythonBuiltinClassType .TypeError , ErrorMessages .ARG_TYPE_MUST_BE , "_thread.excepthook" , "ExceptHookArgs" );
215+ }
216+ SequenceStorage seq = ((PTuple ) exceptHookArgs ).getSequenceStorage ();
217+ if (seq .length () != 4 ) {
218+ throw PRaiseNode .getUncached ().raise (raiseNode , PythonBuiltinClassType .TypeError , ErrorMessages .TAKES_EXACTLY_D_ARGUMENTS_D_GIVEN , 4 , seq .length ());
219+ }
220+
221+ Object excType = SequenceStorageNodes .GetItemScalarNode .executeUncached (seq , 0 );
222+
223+ if (TypeNodes .IsSameTypeNode .executeUncached (excType , PythonBuiltinClassType .SystemExit )) {
224+ return PNone .NONE ;
225+ }
226+ Object excValue = SequenceStorageNodes .GetItemScalarNode .executeUncached (seq , 1 );
227+ Object excTraceback = SequenceStorageNodes .GetItemScalarNode .executeUncached (seq , 2 );
228+ Object thread = SequenceStorageNodes .GetItemScalarNode .executeUncached (seq , 3 );
229+
230+ TruffleString name ;
231+
232+ Object nameAttr = lookupAttr .execute (null , inliningTarget , thread , tsLiteral ("_name" ));
233+ if (nameAttr != null && nameAttr != PNone .NONE && nameAttr != PNone .NO_VALUE ) {
234+ name = strNode .execute (null , inliningTarget , nameAttr );
235+ } else {
236+ Object getIdentBuiltin = lookupAttr .execute (null , inliningTarget , thread , tsLiteral ("get_ident" ));
237+ Object ident = callNode .executeWithoutFrame (getIdentBuiltin );
238+ name = ident != null ? strNode .execute (null , inliningTarget , ident ) : tsLiteral ("<unknown>" );
239+ }
240+
241+ Object sysMod = getContext ().getSysModule ();
242+ Object stdErr = lookupAttr .execute (null , inliningTarget , sysMod , T_STDERR );
243+
244+ boolean stdErrInvalid = stdErr == null || stdErr == PNone .NONE || stdErr == PNone .NO_VALUE ;
245+
246+ if (stdErrInvalid ) {
247+ if (thread != null && thread != PNone .NONE && thread != PNone .NO_VALUE ) {
248+ stdErr = lookupAttr .execute (null , inliningTarget , thread , tsLiteral ("_stderr" ));
249+ }
250+ if (stdErr == null || stdErr == PNone .NONE || stdErr == PNone .NO_VALUE ) {
251+ return PNone .NONE ;
252+ }
253+ }
254+
255+ Object write = lookupAttr .execute (null , inliningTarget , stdErr , tsLiteral ("write" ));
256+ Object flush = lookupAttr .execute (null , inliningTarget , stdErr , tsLiteral ("flush" ));
257+
258+ callNode .executeWithoutFrame (write , tsLiteral ("Exception in thread " ));
259+ callNode .executeWithoutFrame (write , name );
260+ callNode .executeWithoutFrame (write , tsLiteral (":\n " ));
261+ callNode .executeWithoutFrame (flush );
262+
263+ Object sysExcepthook = lookupAttr .execute (null , inliningTarget , sysMod , T___EXCEPTHOOK__ );
264+ if (sysExcepthook != PNone .NO_VALUE && sysExcepthook != PNone .NONE ) {
265+ if (!stdErrInvalid ) {
266+ callNode .executeWithoutFrame (sysExcepthook , excType , excValue , excTraceback );
267+ } else {
268+ Object oldStdErr = lookupAttr .execute (null , inliningTarget , sysMod , T_STDERR );
269+ try {
270+ setAttr .execute (inliningTarget , sysMod , T_STDERR , stdErr );
271+ callNode .executeWithoutFrame (sysExcepthook , excType , excValue , excTraceback );
272+ } finally {
273+ setAttr .execute (inliningTarget , sysMod , T_STDERR , oldStdErr == PNone .NO_VALUE ? PNone .NONE : oldStdErr );
274+ }
275+ }
276+ callNode .executeWithoutFrame (flush );
277+ } else if (excValue instanceof PBaseException ) {
278+ callNode .executeWithoutFrame (write , strNode .execute (null , inliningTarget , excValue ));
279+ callNode .executeWithoutFrame (flush );
280+ }
281+ return PNone .NONE ;
282+ }
283+ }
284+
176285 @ Builtin (name = "start_new_thread" , minNumOfPositionalArgs = 2 , maxNumOfPositionalArgs = 3 )
177286 @ Builtin (name = "start_new" , minNumOfPositionalArgs = 2 , maxNumOfPositionalArgs = 3 )
178287 @ GenerateNodeFactory
0 commit comments