One thing you've missed is that there are situations where there is no
spawning activation. For example, from native code, I can attach a thread,
create a message object and send it a START message. In that situation,
there is no activation to create a stack frame from. Also, the stack frame
in question might be a native activation, so you need to make sure that
these function as well.

Rick

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)
>    -
>
>       this returns the caller's StackFrame
>       -
>
>    define for Activity a field "StringTable *spawnerStackFrameInfo"
>    -
>
>       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    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
>       -
>
>       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
>       -
>
>       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
>
>
> _______________________________________________
> Oorexx-devel mailing list
> Oorexx-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/oorexx-devel
>
_______________________________________________
Oorexx-devel mailing list
Oorexx-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/oorexx-devel

Reply via email to