November 22, 2016

ABAP Bugs and peculiarities


Some of the design decisions in the ABAP language are a leap backwards when the intention was to leap at best sideways to thrust us into the meaningless paradigm of the now-defunct world of 4GL languages.

ABAP is a horrible language on all counts. Syntax-wise and how it is implemented, but here are just some of the succinct little tidbits I have found and ways to try and circumvent them.

1. Divide by zero is OK! sometimes...

So X / 0 in my opinion is never OK. When X != 0, the result is +-Infinity and for X = 0 the result is undefined. Both should result in an error. Well, for some reason ABAP decided 0 / 0 = 0. Without blinking an eyelid. Well done.

To fix this, and create an exception in all cases of X and Y in expression X / Y, re-write it as:

X / Y -> ( X * ( 1 / Y ) )

The same problem happens with modulus, however the solution is not as elegant:

X mod Y -> ( ( 1 mod Y ) * 0 + X / Y ) )

2. float parsing on older system fail when assigned to a non-F variable!

On pre-702 systems (where there are no DECFLOATS either) the following results in an error:

l_packed = '1e-1'.

The problem is that it expects a simple decimal numeric representation, but instead finds a character 'e' in the text. The solution is to assign it to a simple temporary float variable, and then assign it to the packed. For this purpose one can just create a global static float someplace since it is very temporary (unless two floats need to be parsed at the same time. However this can be slow and cumbersome, and then also has the problem of working with multiple values in a single expression.

A better solution is to also use a R/O global float and assign it the value of 1. And then to rewrite the literal float values as:

l_packed = ( cl_global_class=>s_float_unity * '1e-1' ).

3. Unary negation and implied whitespace-imposed precedence.

Take care, in ABAP:

-1 * 3 ** 2 = -9 , and
-3 ** 2 = 9 , BUT

- 3 ** 2 = -9

Almost (but not quite) as bad an idea as spacing in Python that changes meaning. Wow.

4. Problems with CHANGING any via a char1


On older unpatched systems the following program dumps:
REPORT zerror_test.
*-------------------------------------------------------*
CLASS cl_test DEFINITION.
  PUBLIC SECTION.
    METHODS method_true RETURNING value(r_val) TYPE flag.
    METHODS execute CHANGING c_result TYPE any.
ENDCLASS.
*
CLASS cl_test IMPLEMENTATION.
  METHOD method_true.
    " This only dumps with returning C1
 (int + strings are OK) 
    " Thus it could be a unicode/utf-8 underlying string issue?
    r_val = 'X'.
  ENDMETHOD.
  METHOD execute.
    " Assigning here to 'X' instead works in all cases:
    c_result = method_true( ).
  ENDMETHOD.
ENDCLASS.
 
START-OF-SELECTION.
DATA: lcl_test TYPE REF TO cl_test.
CREATE OBJECT lcl_test.

" this works:
DATA: lr_result TYPE REF TO data.
FIELD-SYMBOLS: <l_result> TYPE any.
CREATE DATA lr_result TYPE string.
ASSIGN lr_result->* TO <l_result>.
lcl_test->execute( CHANGING c_result = <l_result> ).
WRITE :/ <l_result>.

" this works as well
DATA: l_result TYPE string.
l_result = lcl_test->method_true( ).
WRITE :/ l_result.

" this does not work:
ASSIGN l_result TO <l_result>.
lcl_test->execute( CHANGING c_result = <l_result> ).
WRITE :/ <l_result>.

" this does not work:
lcl_test->execute( CHANGING c_result = l_result ).
WRITE :/ l_result.

In summary:

1) and 3) above are fundamental flaws and will not and cannot be fixed lest almost all applications start dumping and/or give different results. But 2) and 4) can (and has) been fixed in newer releases.