On Sun, Aug 18, 2024 at 8:29 AM Rony G. Flatscher <rony.flatsc...@wu.ac.at>
wrote:
With the introduction of TraceObject it becomes possible to create and
store trace logs for
further analysis. In March, I started to create a little
tracetool.rex/tracutil.cls to help
the Rexx programmers analyze their code. Testing and analyzing what can be
inferred, it turned
out that in the case of a started message and a reply statement, no
information about the
caller/invoker is currently available, making it very difficult, if not
impossible, at times
to find out which instruction caused the creation of a new activity.
In the discussions on the developer list, it became clear that storing a
StackFrame object
itself may cause crashes if the activity for which it was created has gone
away if I
understood the problem correctly. Therefore, TraceObject would not store
the StackFrame object
but rather create a StringTable receiving the relevant StackFrame
information such that
accessing the StringTable later would not cause any problems.
This information gets only added to TraceObject if a
TRACE_PREFIX_INVOCATION is active
(tracing the invocation of a routine or method).
Looking around in the source code for ways to safely make the caller's
stackframe information
available in the case that a new activity gets spawned as a result of a
start message or a
reply keyword statement, the following idea would be proposed, hoping that
it is safe and does
not impact the interpreter unduly:
*
define a method Activity::generateCallerStackFrame(bool skipFirst)
o
this returns the caller's StackFrame
*
define for Activity a field "StringTable *spawnerStackFrameInfo"
o
this is used to store a StringTable with the caller's StackFrame
information
*
define a method Activity::setCallerStackFrameAsStringTable(Activity
*oldActivity, Activity
*newActivity, bool skipFirst)
o
o this method gets only invoked in "Method::start()" and in the
reply section of
"RexxActivation::run(...)", which are the two cases where a new
activity gets spawned
to execute the message in the case of "Method:start()" and the
remaining instructions
in the case of a reply keyword instruction
o
this method queries the caller's stackframe using
Activity::generateCallerStackFrame(skipFirst) and using
"RexxActivation::getStackFrameAsStringTable(StackFrameClass *
stackFrame)" returning a
StringTable with the stackframe information and storing it with
"spawnerStackFrameInfo" field
o
in addition, it will get an entry THREAD with the thread ID added,
such that a) the
correct invocation can be located (the same location could be used
over and over from
different threads; it is important to become able to identify which
activity/thread
was used) and b) one can distinguish with the callerStackFrame in
TraceObject whether
it got created
An experimental implementation seems to work, and so far, no adverse
behavior has been observed.
Here the diff:
*Index: interpreter/classes/MessageClass.cpp*
===================================================================
--- interpreter/classes/MessageClass.cpp (revision 12859)
+++ interpreter/classes/MessageClass.cpp (working copy)
@@ -537,6 +537,10 @@
// spawn a new activity off of the old activity
Activity *oldActivity = ActivityManager::currentActivity;
Activity *newActivity = oldActivity->spawnReply();
+ + // save current frame info in a StringTable as it has caused the
creation of a new
activity + Activity::setCallerStackFrameAsStringTable(oldActivity,
newActivity, true); //
create stackframe from parent/caller +
// mark which activity we're running on, then dispatch the message
// on the new activity (which is sitting waiting for work to
perform)
setField(startActivity, newActivity);
*Index: interpreter/concurrency/Activity.cpp*
===================================================================
--- interpreter/concurrency/Activity.cpp (revision 12859)
+++ interpreter/concurrency/Activity.cpp (working copy)
@@ -1159,31 +1159,54 @@
}
+
/**
- * Generates a StackFrame from the parent and returns it.
+ * Generate the caller's stack frame.
*
- * @return parent's StackFrame or NULLOBJECT, if no parent exists
+ * @param skipFirst Determines if we should skip the first frame: + *
false for reply in
RexxActivation::run(...), + * true for Message::start() and in the
general case. + * + *
@return The stackframe of the caller which caused a spawned activity.
*/
-StackFrameClass* Activity::generateParentStackFrame()
+StackFrameClass* Activity::generateCallerStackFrame(bool skipFirst)
{
- // create lists for both the stack frames and the traceback lines
- StackFrameClass *parentStackFrame = NULLOBJECT;
-
ActivationFrame *frame = activationFrames;
+ StackFrameClass *callerStackFrame = NULL;
- if (frame != NULL)
+ if (frame && skipFirst)
{
- frame = frame->next; // get parent
- if (frame != NULL)
- {
- parentStackFrame = frame->createStackFrame();
- }
+ frame = frame->next;
}
- return parentStackFrame;
+ if (frame) + { + callerStackFrame = frame->createStackFrame(); + } +
return
callerStackFrame;
}
/**
+ * Generates and saves a StringTable to store stackFrame infos of
oldActivity in
newActivity + * (for TraceObject), used by MessageClass::start(). + * +
*/ +void
Activity::setCallerStackFrameAsStringTable(Activity *oldActivity,
Activity *newActivity,
bool skipFirst) +{ + // create lists for both the stack frames and the
traceback lines +
StringTable *spawnerStackFrameInfo = NULLOBJECT; + + StackFrameClass
*oldActivityStackFrame = oldActivity ->
generateCallerStackFrame(skipFirst); + // returns
a StringTable that may be empty if oldActivityStackFrame==NULLOBJECT +
newActivity ->
spawnerStackFrameInfo=RexxActivation::getStackFrameAsStringTable(oldActivityStackFrame);
+
// save thread ID to indicate from which thread ID the spawnReply()
came from (helpful, if
StringTable is empty) + newActivity -> spawnerStackFrameInfo ->
put(new_integer(oldActivity -> getIdntfr()), GlobalNames::THREAD ); +
return; +}
+
+
+
+/**
* Build a message and perform the indicated substitutions.
*
* @param messageCode
*Index: interpreter/concurrency/Activity.hpp*
===================================================================
--- interpreter/concurrency/Activity.hpp (revision 12859)
+++ interpreter/concurrency/Activity.hpp (working copy)
@@ -159,8 +159,12 @@
void unwindToFrame(RexxActivation *frame);
void cleanupStackFrame(ActivationBase *poppedStackFrame);
ArrayClass *generateStackFrames(bool skipFirst);
- StackFrameClass *generateParentStackFrame();
+ // allow TraceObject's callerStackFrame entry to indicate the caller that caused the
spawned activity + StackFrameClass*
Activity::generateCallerStackFrame(bool ); + static
void Activity::setCallerStackFrameAsStringTable(Activity *oldActivity,
Activity
*newActivity, bool); + StringTable *spawnerStackFrameInfo;
+
Activity *spawnReply();
void exitKernel();
*Index: interpreter/execution/RexxActivation.cpp*
===================================================================
--- interpreter/execution/RexxActivation.cpp (revision 12859)
+++ interpreter/execution/RexxActivation.cpp (working copy)
@@ -702,6 +702,9 @@
activity = oldActivity->spawnReply();
+ // save current frame info in a StringTable as it has caused the creation of a new
activity + Activity::setCallerStackFrameAsStringTable(oldActivity,
activity, false);
+
// save the pointer to the start of our stack frame.
We're
// going to need to release this after we migrate
everything
// over.
@@ -5157,8 +5160,23 @@
}
else if (tracePrefix == TRACE_PREFIX_INVOCATION) // tracing an
invocation entry
{
- StackFrameClass *stackFrame = activity ->
generateParentStackFrame();
- traceObject -> put(stackFrame ?
getStackFrameAsStringTable(stackFrame) : TheNilObject,
GlobalNames::CALLERSTACKFRAME);
+ StackFrameClass *stackFrame = activity ->
generateCallerStackFrame(true); + if
(stackFrame) + { + traceObject ->
put(getStackFrameAsStringTable(stackFrame),
GlobalNames::CALLERSTACKFRAME); + } + else + { + if (activity ->
spawnerStackFrameInfo) //
spawner's frame infos saved as a StringTable? + { + // allows for
analyzing trace logs to
identify the caller of a spawned activity + traceObject -> put(activity
->
spawnerStackFrameInfo, GlobalNames::CALLERSTACKFRAME); + } + else + { +
traceObject ->
put(TheNilObject, GlobalNames::CALLERSTACKFRAME); + } + }
}
// METHODCALL, fill in method related information
@@ -5232,26 +5250,28 @@
* the identityHash of the StackFrame entries EXECUTABLE and TARGET
if they are not
* .nil.
*
-* @param stackFrame the StackFrame object to use
-* @return a StringTable with all the entries of stackFrame
+* @param stackFrame the StackFrame object to use, may be NULLOBJECT +*
@return a
StringTable with all the entries of stackFrame, if available
*/
StringTable *
RexxActivation::getStackFrameAsStringTable(StackFrameClass * stackFrame)
{
- ProtectedObject result;
StringTable *tmpStringTable = new_string_table();
- // array
- tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::ARGUMENTS , result),
GlobalNames::ARGUMENTS );
+ if (stackFrame != NULLOBJECT) + { + ProtectedObject result; + //
array + tmpStringTable
-> put(stackFrame->sendMessage(GlobalNames::ARGUMENTS , result), GlobalNames::ARGUMENTS );
- // strings
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::LINE
, result), GlobalNames::LINE );
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::NAME
, result), GlobalNames::NAME );
- tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::TRACELINE , result),
GlobalNames::TRACELINE );
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::TYPE
, result), GlobalNames::TYPE );
+ // strings + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::LINE , result),
GlobalNames::LINE ); + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::NAME ,
result), GlobalNames::NAME ); + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::TRACELINE , result),
GlobalNames::TRACELINE ); +
tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::TYPE ,
result),
GlobalNames::TYPE );
- // objects
- tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::EXECUTABLE, result),
GlobalNames::EXECUTABLE);
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::TARGET
, result), GlobalNames::TARGET );
-
+ // objects + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::EXECUTABLE,
result), GlobalNames::EXECUTABLE); + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::TARGET , result),
GlobalNames::TARGET );
+ }
return tmpStringTable;
}
To get at the patch directly, I created a RFE with the above patch at
<https://sourceforge.net/p/oorexx/feature-requests/842/>
<https://sourceforge.net/p/oorexx/feature-requests/842/>.
Any comments, suggestions?
---rony