Skip to Content
Author's profile photo Mike Pokraka

ABAP traps: Lifetime of functional operand values

A nasty bug-hunt led me to the discovery of an unexpected behaviour of ABAP, which I thought I’d share:

Usually ABAP’s data elements’ scope and lifetime is across an entire procedure. If you code an inline declaration such as data(x) inside a loop, x will continue to exist outside the loop.

Even helper variables within functional expressions are procedure-global, as I found out in How local is a ‘local auxiliary field’?

ABAP doesn’t do stuff local to a loop… or so I thought!

The scenario is simple: We have a loop, and at the end of the loop we want to re-use the last value:

loop at itab into val. 
  ...
endloop.
write: / |The last value was: { val }|.

This also works if the source is a functional operand (e.g. functional method):

loop at obj->method_that_returns_itab( ) into val. 
  ...
endloop.
write: / |The last value was: { val }|.

But sometimes we may use field symbols, and this is where the fun starts:

loop at  obj->method_that_returns_itab( ) assigning field-symbol(<val>). 
  write <val>.  "<--- OK!
endloop.
write: / |The last value was: { <val> }|.  "<--- Dump! Field symbol not assigned

Here a functional operand is used effectively as a sort of anonymous data element: the method call is completed when the loop starts up, and the field symbol continues to point to the value and is moved down the table elements as the loop progresses.

Yet after the loop is exited, the value is no longer accessible via field symbol or reference. I did not expect this loop-local lifetime. Another unexpected quirk of ABAP  🙂

Update: 

As Shai Sinai helpfully pointed out in the comments: this is even mentioned in the documentation:

“If the internal table is specified as the return value or result of a functional method, a constructor expression, or a table expression, the value is persisted for the duration of the loop. Afterwards, it is no longer possible to access the internal table. ”

Documented, but still unexpected.

Full test program below.

REPORT z_functional_operand_lifetime.

CLASS lcl_test DEFINITION CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS start.
  PRIVATE SECTION.
    METHODS get_strings RETURNING VALUE(result) TYPE stringtab.
ENDCLASS.

CLASS lcl_test IMPLEMENTATION.

  METHOD get_strings.
    result = VALUE #( ( `A` ) ( `B` ) ( `C` ) ).
  ENDMETHOD.

  METHOD start.
    DATA: s    TYPE string,
          sref TYPE REF TO string.
    FIELD-SYMBOLS <s> TYPE string.

    WRITE: / 'LOOP AT strings ASSIGNING <s>.'.
    DATA(strings) = get_strings( ).
    LOOP AT strings ASSIGNING <s>.
      WRITE: / <s>.
    ENDLOOP.
    WRITE: / COND #( WHEN <s> IS ASSIGNED
                     THEN |Assigned: { <s> }|
                     ELSE 'Not assigned' ).
    ULINE.

    WRITE: / 'LOOP AT get_strings( ) INTO s.'.
    LOOP AT get_strings( ) INTO s.
      WRITE: / s.
    ENDLOOP.
    WRITE: / COND #( WHEN <s> IS NOT INITIAL
                     THEN |Value: { s }|
                     ELSE 'Not assigned' ).
    ULINE.

    WRITE: / 'LOOP AT get_strings( ) ASSIGNING <s>.'.
    LOOP AT get_strings( ) ASSIGNING <s>.
      WRITE: / <s>.
    ENDLOOP.
    WRITE: / COND #( WHEN <s> IS ASSIGNED
                     THEN |Assigned: { <s> }|
                     ELSE 'Not assigned' ).
    ULINE.

    WRITE: / 'LOOP AT get_strings( ) REFERENCE INTO sref.'.
    LOOP AT get_strings( ) REFERENCE INTO sref.
      WRITE: / sref->*.
    ENDLOOP.
    WRITE: / COND #( WHEN sref IS BOUND
                     THEN |Bound: { sref->* }|
                     ELSE 'Not bound' ).
  ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.
  NEW lcl_test( )->start( ).

Assigned Tags

      14 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Matthew Billingham
      Matthew Billingham

      It makes sense though. The data returned by the functional method only exists within the loop, so any field symbol assigned to it will not be assigned outside the loop.

      Author's profile photo Mike Pokraka
      Mike Pokraka
      Blog Post Author

      I understood it once I saw how it behaves, but I don't think it's obvious that the data returned by the functional method should only exist within the loop.

      Consider two other scenarios:

      LOOP AT ... 
        x = VALUE #( FOR helper IN itab ... ).
      ENDLOOP. 
      
      data(name) = 'HELPER'. 
      assign (name) to FIELD-SYMBOL(<fs>). 
      write <fs>.   "Here it is!
      

      --> Scope is wider than expected

      Or

      CLASS lcl_data IMPLEMENTATION. 
      METHOD get_ref. 
        GET REFERENCE OF me->someattribute INTO result. 
      ENDMETHOD.
      
      "skip forward to calling code:
      data(ref) = dataobj->get_ref( ). 
      FREE dataobj. 
      write ref->*.   "Yep, still there!

      So yes, it makes sense with an understanding of the subtleties of stacks and heaps and temporary data objects, but on a superficial level it can look confusing.

      Author's profile photo Shai Sinai
      Shai Sinai

      Well,
      Just to clarify:
      This is standard behavior of LOOP AT … ASSIGNING <fs>, regardless the functional method call nor the inline field-symbol declaration.

      Author's profile photo Mike Pokraka
      Mike Pokraka
      Blog Post Author

      No, it's not the standard behaviour regardless of functional method calls. It behaves one way with a local itab and another with a functional operand. That's the interesting bit that prompted me to blog it, because it's easy to overlook if you're not aware of it.

      Copy/paste the code into a test program and you'll see in the first example the assignment remains beyond the loop.

      Author's profile photo Shai Sinai
      Shai Sinai

      Surprisingly,
      I could swear this is standard behavior of LOOP AT … <ASSIGNING> (Field symbol isn't assigned anymore after loop), but I double-checked it right now and it seems you are right.

      Hence, the inconsistent behavior with functional method calls seems like a bug. You should open an incident regarding it.

       

       

      Author's profile photo Mike Pokraka
      Mike Pokraka
      Blog Post Author

      I don't believe it's a bug, it's just one of the quirks of ABAP.

      Theoretically you shouldn't be able to use field symbols in this way at all, because, according to SAP, assigning a temporary data object to a field symbol would not make sense. But in a LOOP statement it suddenly makes sense 🙂

      The trick here is that it's not about the assignment, but how long the result of the functional method call result exists in memory, in this case it exists throughout the LOOP-ENDLOOP statement block and then disappears, the field symbol no longer has anything to point to. But if using a local variable, it still exists and the field symbol remains valid.

      Author's profile photo Shai Sinai
      Shai Sinai

      You got a point there!

      I think it's even mentioned in the documentation:

      "If the internal table is specified as the return value or result of a functional method, a constructor expression, or a table expression, the value is persisted for the duration of the loop. Afterwards, it is no longer possible to access the internal table. "

       

       

      Author's profile photo Mike Pokraka
      Mike Pokraka
      Blog Post Author

      You are right! I somehow missed that.

      But the main point was that it's not obvious and easy to make that mistake when refactoring. And it goes the opposite way to my second example to Matthew above: CLEAR/FREE is also meant to make stuff disappear, but it doesn't. Them's the quirks of ABAP.

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Interesting. That's what I assumed too.

      Actually I'd think that the bug is that reference still exists afterwards. "What happens in the loop, stays in the loop". 🙂

       

      Author's profile photo Mike Pokraka
      Mike Pokraka
      Blog Post Author

      I expected the reference pointing to a variable to continue to exist, it's same way as when you do a LOOP ... INTO data(x) - the variable x continues to hold the last value we put in it until the procedure is exited.

      Author's profile photo Quynh Doan Manh
      Quynh Doan Manh

      I think it makes sense. Since the field symbol pointed to the mathematic result, after the calculation done ( end of loop),ABAP return from get_strings( ) method to the main program it mean the method itself not exist in the main program anymore then the field symbol assigment also end. In other case you already assigned the result to a helper variable then field symbol pointed to that helper variable then ofcouse it would be exist after the loop.

      Author's profile photo Sandra Rossi
      Sandra Rossi

      Hi Mike, thanks for your post. Two remarks: 1) a little typo: loop at obj->method_that_returns_itab( ) into field-symbol(<val>) gives as syntax error, should the INTO be changed to ASSIGNING.

      2) you could update your post (for those who don't read the comments) to include the essential post of Shai Sinai June 21, 2018 at 12:45 pm (documentation: “If the internal table is specified as the return value or result of a functional method, a constructor expression, or a table expression, the value is persisted for the duration of the loop. Afterwards, it is no longer possible to access the internal table. ”)

       

      Author's profile photo Mike Pokraka
      Mike Pokraka
      Blog Post Author

      Thanks Sandra, good points! I have updated the blog with both suggestions.

      Author's profile photo Horst Keller
      Horst Keller

      Sometimes, I'm astonished myself, what I've found out and written to the documentation ...

      Believe me, I'm just hacking around and trying things like you do and hope that you read it before trying yourself. But alas, it's just too much stuff to bring it to any point.