I had a few difficulties after refactoring some code via Extract Method in ADT (Ctrl+Shift+M), I thought that it was a safe operation, which should not lead to regressions, but the code failed with errors like the exception class CX_SY_DYN_CALL_ILLEGAL_TYPE or the runtime error MOVE_TO_LIT_NOTALLOWED_NODATA.
These errors are due to the read-only flag which is set at runtime on data objects (importing parameters by reference, internal table key fields, explicit READ-ONLY public class attributes).
I will try to explain what this internal flag is, and how to work around this limitation.
Â
The internal read-only flag is set either at runtime or at compile times, as follows:
“The key fields of the primary table key of sorted tables and hashed tables are always read-only.” Source: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abenitab_key_primary.htm.For methods and function modules, “Input parameters for which pass by reference is defined must not be accessed with write access in the procedure.” Source: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abeninput_parameter_glosry.htm.The direct access to a public class attribute defined explicitly as READ-ONLY, from outside the class. Source: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abapdata_options.htm#!ABAP_ADDITION_2@2@. The attribute can be changed if the attribute is accessed from inside the class or from a friend class, and it remains ignored if this class or friend class passes the attribute by reference to a procedure outside the class or friend class.If a reference is created from a read-only field obtained by one of the three previous cases, it inherits the internal read-only flag (whatever it’s off or on).
Â
CX_SY_DYN_CALL_ILLEGAL_TYPE when calling a method
I wanted to refactor this code:
DATA mandants TYPE SORTED TABLE OF t000 WITH UNIQUE KEY mandt.
mandants = VALUE #( ( mandt = ‘100’ mtext = ‘ test’ ) ).
LOOP AT mandants ASSIGNING FIELD-SYMBOL(<mandant>).
<mandant>-mtext = condense( <mandant>-mtext ).
ENDLOOP.
I refactored the code by applying Extract Method to the line inside the loop. The method CONDENSE_MANDANT_TEXT was created:
DATA mandants TYPE SORTED TABLE OF t000 WITH UNIQUE KEY mandt.
mandants = VALUE #( ( mandt = ‘100’ mtext = ‘ test’ ) ).
LOOP AT mandants ASSIGNING FIELD-SYMBOL(<mandant>).
condense_mandant_text( CHANGING c_mandant = <mandant> ).
ENDLOOP.
ENDMETHOD.
METHOD condense_mandant_text.
c_mandant-mtext = condense( c_mandant-mtext ).
ENDMETHOD.
When running this refactored code, the exception CX_SY_DYN_CALL_ILLEGAL_TYPE happens when attempting to call the method CONDENSE_MANDANT_TEXT (it’s not even called).
This CX_SY_DYN_CALL_ILLEGAL_TYPE exception had this message: “Call of the method CONDENSE_MANDANT_TEXT of the class LCL_APP has failed; the actual parameter for C_MANDANT is write-protected”
This error happened due to the fact that the internal table MANDANTS is of type « SORTED » (it would also happen with « HASHED ») with key field MANDT, so this key field is implicitly read-only.
Â
There are two workarounds:
1/ The “easiest” (*) way: change the internal table primary key to STANDARD and use a secondary key instead.
DATA mandants TYPE STANDARD TABLE OF t000 WITH EMPTY KEY
WITH UNIQUE SORTED KEY by_mandt COMPONENTS mandt.
Be careful, a unique secondary key may be a problem if one of its key fields is updated, it would lead to the runtime error MOVE_TO_LIT_NOTALLOWED_NODATA. If that’s the case, see the next chapter.
(*) In fact, this solution is not so easy because moving a key from primary to secondary may change the behavior like a duplicate unique key may not be seen immediately if a Delayed Update happens (see Delayed Update: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abenitab_key_secondary_update.htm). You also have to verify all reads of the internal table and indicate explicitly if needed the use of the secondary key (e.g. mandants[ KEY by_mandt COMPONENTS mandt = ‘100’ ]). See also the recommendations when to use or not to use secondary keys: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abenitab_key_secondary_usage.htm.
Â
2/ The second solution is to transfer the whole internal table instead of one line i.e., refactor the whole loop:
condense_all_mandant_texts( CHANGING c_mandants = mandants ).
ENDMETHOD.
METHOD condense_all_mandant_texts.
LOOP AT c_mandants ASSIGNING FIELD-SYMBOL(<mandant>).
<mandant>-mtext = condense( <mandant>-mtext ).
ENDLOOP.
ENDMETHOD.
Â
Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA Assignment error: Overwriting of a protected field.
The examples below all lead to the runtime error MOVE_TO_LIT_NOTALLOWED_NODATA if the sorted or hashed internal table has a primary key:
DATA mandants TYPE SORTED TABLE OF t000 WITH UNIQUE KEY mandt.
1/ Because it’s a key field:
mandants[ 1 ]-mandt = ‘100’. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA
OR
DATA(ref_mandant) = REF #( mandants[ 1 ] ).
ref_mandant->mandt = ‘100’. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA
Â
NB: in both cases, it’s possible to maintain any non-key field e.g., MTEXT:
mandants[ 1 ]-mtext = test’. ” No error
ref_mandant->mtext = test’. ” No error
Â
2/ Because the whole line is updated and contains a key field, although the key field is “changed to the same value”:
DATA(new_mandant) = VALUE t000( mandt = ‘100’ mtext = ‘ test 2’ ).
mandants[ 1 ] = new_mandant. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA
Â
3/ Because the reference was obtained from an importing parameter:
DATA(ref_mandant) = get_mandant_reference( mandants[ 1 ] ).
ref_mandant->mtext = ‘test’. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA
METHODS get_mandant_reference
IMPORTING i_mandant TYPE t000 ” i_mandant is assigned the read-only flag
RETURNING VALUE(r_ref_mandant) TYPE REF TO t000.
METHOD get_mandant_reference.
r_ref_mandant = REF #( i_mandant ). ” the reference inherits the read-only flag from i_mandant
ENDMETHOD.
Â
The only workaround for changing the key fields in the case 1, is to change the internal table primary key to STANDARD (or any key which doesn’t contain the key fields to update) and use a secondary key instead, which lead to either a Lazy Update (non-unique secondary key) or a Delayed Update (unique secondary key, but the update must be done only via a data reference or a field symbol).
DATA mandants TYPE STANDARD TABLE OF t000 WITH EMPTY KEY
WITH UNIQUE SORTED KEY by_mandt COMPONENTS mandt.
The change of a key field will update the secondary index only the next time the internal table is read. This will NOT produce a runtime error:
mandants = VALUE #( ( mandt = ‘100’ mtext = ‘ test’ )
( mandt = ‘110’ mtext = ‘test 2’ ) ).
DATA(ref_mandant) = REF #( mandants[ 1 ] ).
DATA(ref_mandant_2) = REF #( mandants[ 2 ] ).
ref_mandant->mandt = ‘110’.
ref_mandant_2->mandt = ‘100’.
For more information about Lazy Update and Delayed Update, see: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abenitab_key_secondary_update.htm.
Be careful, the next statement leads to the runtime error ITAB_DUPLICATE_KEY because it’s a unique key and the change is done neither via a data reference nor via a field-symbol and so the index is updated immediately instead of using a Delayed Update:
mandants[ 1 ]-mandt = ‘110’.
Â
In the case 2, it’s possible to use MODIFY TABLE to avoid the runtime error, it ignores the key fields:
MODIFY TABLE mandants FROM new_mandant.
If you know the line index to update (sorted tables only), you may also use the line index number, provided that the key values are equal to the ones of the modified line (otherwise, the runtime error ITAB_ACTIVE_KEY_VIOLATION happens):
MODIFY mandants INDEX 1 FROM new_mandant.
Â
In the case 3, you may use CHANGING instead of IMPORTING to be able to write to the data reference:
DATA(ref_mandant) = get_mandant_reference( CHANGING c_mandant = mandants[ 1 ] ).
ref_mandant->mtext = ‘test’. ” No runtime error
METHODS get_mandant_reference
CHANGING c_mandant TYPE t000
RETURNING VALUE(r_ref_mandant) TYPE REF TO t000.
METHOD get_mandant_reference.
r_ref_mandant = REF #( c_mandant ).
ENDMETHOD.
Â
Compiler warning: The “ITAB” is key table. Writes cannot be performed on a full row and produce runtime errors.
For information, the compiler may send a warning if it detects an attempt to change a line from an internal table with a sorted or hashed primary key e.g., it can be any of these two situations:
condense_mandant_text( CHANGING c_mandant = mandants[ 1 ] ). ” Exception CX_SY_DYN_CALL_ILLEGAL_TYPE
mandants[ 1 ] = new_mandant. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA
The warning (that’s the message MESSAGEGJO in table TRMSG): The “MANDANTS” is key table. Writes cannot be performed on a full row and produce runtime errors.
It’s a warning but if the code above runs, it systematically raises an exception or a runtime error.
Â
Conclusion
As far as I understand, the parameters, references or field symbols are each made of:
A pointer to a data object i.e., the value of a variable or constant. This pointer is probably the memory offset, possibly the length.Read-only flag (true or false)Internal table to which the data belongs (if applicable). It helps the system know which are the key fields and what index to update if a key field is changed.NB: unrelated to read-only, a fourth attribute could be considered, which is the data type, either the original one or the one obtained via casting (ASSIGN CASTING).
After this blog post, you should know:
That Extract Method can lead to regressionsHow the read-only flag worksHow to avoid and solve the exception CX_SY_DYN_CALL_ILLEGAL_TYPE and the runtime error MOVE_TO_LIT_NOTALLOWED_NODATAThat the secondary indexes can be updated immediately or late via Lazy Update or Delayed UpdateÂ
​ I had a few difficulties after refactoring some code via Extract Method in ADT (Ctrl+Shift+M), I thought that it was a safe operation, which should not lead to regressions, but the code failed with errors like the exception class CX_SY_DYN_CALL_ILLEGAL_TYPE or the runtime error MOVE_TO_LIT_NOTALLOWED_NODATA.These errors are due to the read-only flag which is set at runtime on data objects (importing parameters by reference, internal table key fields, explicit READ-ONLY public class attributes).I will try to explain what this internal flag is, and how to work around this limitation. The internal read-only flag is set either at runtime or at compile times, as follows:“The key fields of the primary table key of sorted tables and hashed tables are always read-only.” Source: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abenitab_key_primary.htm.For methods and function modules, “Input parameters for which pass by reference is defined must not be accessed with write access in the procedure.” Source: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abeninput_parameter_glosry.htm.The direct access to a public class attribute defined explicitly as READ-ONLY, from outside the class. Source: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abapdata_options.htm#!ABAP_ADDITION_2@2@. The attribute can be changed if the attribute is accessed from inside the class or from a friend class, and it remains ignored if this class or friend class passes the attribute by reference to a procedure outside the class or friend class.If a reference is created from a read-only field obtained by one of the three previous cases, it inherits the internal read-only flag (whatever it’s off or on). CX_SY_DYN_CALL_ILLEGAL_TYPE when calling a methodI wanted to refactor this code: DATA mandants TYPE SORTED TABLE OF t000 WITH UNIQUE KEY mandt.
mandants = VALUE #( ( mandt = ‘100’ mtext = ‘ test’ ) ).
LOOP AT mandants ASSIGNING FIELD-SYMBOL(<mandant>).
<mandant>-mtext = condense( <mandant>-mtext ).
ENDLOOP.I refactored the code by applying Extract Method to the line inside the loop. The method CONDENSE_MANDANT_TEXT was created: DATA mandants TYPE SORTED TABLE OF t000 WITH UNIQUE KEY mandt.
mandants = VALUE #( ( mandt = ‘100’ mtext = ‘ test’ ) ).
LOOP AT mandants ASSIGNING FIELD-SYMBOL(<mandant>).
condense_mandant_text( CHANGING c_mandant = <mandant> ).
ENDLOOP.
ENDMETHOD.
METHOD condense_mandant_text.
c_mandant-mtext = condense( c_mandant-mtext ).
ENDMETHOD.When running this refactored code, the exception CX_SY_DYN_CALL_ILLEGAL_TYPE happens when attempting to call the method CONDENSE_MANDANT_TEXT (it’s not even called).This CX_SY_DYN_CALL_ILLEGAL_TYPE exception had this message: “Call of the method CONDENSE_MANDANT_TEXT of the class LCL_APP has failed; the actual parameter for C_MANDANT is write-protected”This error happened due to the fact that the internal table MANDANTS is of type « SORTED » (it would also happen with « HASHED ») with key field MANDT, so this key field is implicitly read-only. There are two workarounds:1/ The “easiest” (*) way: change the internal table primary key to STANDARD and use a secondary key instead. DATA mandants TYPE STANDARD TABLE OF t000 WITH EMPTY KEY
WITH UNIQUE SORTED KEY by_mandt COMPONENTS mandt.Be careful, a unique secondary key may be a problem if one of its key fields is updated, it would lead to the runtime error MOVE_TO_LIT_NOTALLOWED_NODATA. If that’s the case, see the next chapter.(*) In fact, this solution is not so easy because moving a key from primary to secondary may change the behavior like a duplicate unique key may not be seen immediately if a Delayed Update happens (see Delayed Update: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abenitab_key_secondary_update.htm). You also have to verify all reads of the internal table and indicate explicitly if needed the use of the secondary key (e.g. mandants[ KEY by_mandt COMPONENTS mandt = ‘100’ ]). See also the recommendations when to use or not to use secondary keys: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abenitab_key_secondary_usage.htm. 2/ The second solution is to transfer the whole internal table instead of one line i.e., refactor the whole loop: condense_all_mandant_texts( CHANGING c_mandants = mandants ).
ENDMETHOD.
METHOD condense_all_mandant_texts.
LOOP AT c_mandants ASSIGNING FIELD-SYMBOL(<mandant>).
<mandant>-mtext = condense( <mandant>-mtext ).
ENDLOOP.
ENDMETHOD. Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA Assignment error: Overwriting of a protected field.The examples below all lead to the runtime error MOVE_TO_LIT_NOTALLOWED_NODATA if the sorted or hashed internal table has a primary key:DATA mandants TYPE SORTED TABLE OF t000 WITH UNIQUE KEY mandt.1/ Because it’s a key field:mandants[ 1 ]-mandt = ‘100’. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATAORDATA(ref_mandant) = REF #( mandants[ 1 ] ).
ref_mandant->mandt = ‘100’. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA NB: in both cases, it’s possible to maintain any non-key field e.g., MTEXT:mandants[ 1 ]-mtext = test’. ” No error
ref_mandant->mtext = test’. ” No error 2/ Because the whole line is updated and contains a key field, although the key field is “changed to the same value”: DATA(new_mandant) = VALUE t000( mandt = ‘100’ mtext = ‘ test 2’ ).
mandants[ 1 ] = new_mandant. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATAÂ 3/ Because the reference was obtained from an importing parameter:DATA(ref_mandant) = get_mandant_reference( mandants[ 1 ] ).
ref_mandant->mtext = ‘test’. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATA
METHODS get_mandant_reference
IMPORTING i_mandant TYPE t000 ” i_mandant is assigned the read-only flag
RETURNING VALUE(r_ref_mandant) TYPE REF TO t000.
METHOD get_mandant_reference.
r_ref_mandant = REF #( i_mandant ). ” the reference inherits the read-only flag from i_mandant
ENDMETHOD. The only workaround for changing the key fields in the case 1, is to change the internal table primary key to STANDARD (or any key which doesn’t contain the key fields to update) and use a secondary key instead, which lead to either a Lazy Update (non-unique secondary key) or a Delayed Update (unique secondary key, but the update must be done only via a data reference or a field symbol). DATA mandants TYPE STANDARD TABLE OF t000 WITH EMPTY KEY
WITH UNIQUE SORTED KEY by_mandt COMPONENTS mandt.The change of a key field will update the secondary index only the next time the internal table is read. This will NOT produce a runtime error: mandants = VALUE #( ( mandt = ‘100’ mtext = ‘ test’ )
( mandt = ‘110’ mtext = ‘test 2’ ) ).
DATA(ref_mandant) = REF #( mandants[ 1 ] ).
DATA(ref_mandant_2) = REF #( mandants[ 2 ] ).
ref_mandant->mandt = ‘110’.
ref_mandant_2->mandt = ‘100’.For more information about Lazy Update and Delayed Update, see: https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/index.htm?file=abenitab_key_secondary_update.htm.Be careful, the next statement leads to the runtime error ITAB_DUPLICATE_KEY because it’s a unique key and the change is done neither via a data reference nor via a field-symbol and so the index is updated immediately instead of using a Delayed Update:mandants[ 1 ]-mandt = ‘110’. In the case 2, it’s possible to use MODIFY TABLE to avoid the runtime error, it ignores the key fields:MODIFY TABLE mandants FROM new_mandant.If you know the line index to update (sorted tables only), you may also use the line index number, provided that the key values are equal to the ones of the modified line (otherwise, the runtime error ITAB_ACTIVE_KEY_VIOLATION happens):MODIFY mandants INDEX 1 FROM new_mandant. In the case 3, you may use CHANGING instead of IMPORTING to be able to write to the data reference:DATA(ref_mandant) = get_mandant_reference( CHANGING c_mandant = mandants[ 1 ] ).
ref_mandant->mtext = ‘test’. ” No runtime error
METHODS get_mandant_reference
CHANGING c_mandant TYPE t000
RETURNING VALUE(r_ref_mandant) TYPE REF TO t000.
METHOD get_mandant_reference.
r_ref_mandant = REF #( c_mandant ).
ENDMETHOD. Compiler warning: The “ITAB” is key table. Writes cannot be performed on a full row and produce runtime errors.For information, the compiler may send a warning if it detects an attempt to change a line from an internal table with a sorted or hashed primary key e.g., it can be any of these two situations:condense_mandant_text( CHANGING c_mandant = mandants[ 1 ] ). ” Exception CX_SY_DYN_CALL_ILLEGAL_TYPE
mandants[ 1 ] = new_mandant. ” Runtime error MOVE_TO_LIT_NOTALLOWED_NODATAThe warning (that’s the message MESSAGEGJO in table TRMSG): The “MANDANTS” is key table. Writes cannot be performed on a full row and produce runtime errors.It’s a warning but if the code above runs, it systematically raises an exception or a runtime error. ConclusionAs far as I understand, the parameters, references or field symbols are each made of:A pointer to a data object i.e., the value of a variable or constant. This pointer is probably the memory offset, possibly the length.Read-only flag (true or false)Internal table to which the data belongs (if applicable). It helps the system know which are the key fields and what index to update if a key field is changed.NB: unrelated to read-only, a fourth attribute could be considered, which is the data type, either the original one or the one obtained via casting (ASSIGN CASTING).After this blog post, you should know:That Extract Method can lead to regressionsHow the read-only flag worksHow to avoid and solve the exception CX_SY_DYN_CALL_ILLEGAL_TYPE and the runtime error MOVE_TO_LIT_NOTALLOWED_NODATAThat the secondary indexes can be updated immediately or late via Lazy Update or Delayed Update   Read More Technology Blog Posts by Members articlesÂ
#SAP
#SAPTechnologyblog