Skip to Content
Technical Articles
Author's profile photo Edo von Glan

Code Search in Modifications and Enhancements (without HANA)

As a long time SAP customer, you usually have a lot of code modifications and enhancements.

Wouldn’t it be useful sometimes to be able to search that code?

If you are already on HANA, you can use the search in Eclipse: https://blogs.sap.com/2014/01/23/abap-sourcecode-search/

But our main system ist not yet running HANA.

So, I modified SAPs standard search report RS_ABAP_SOURCE_SCAN to search for modifications and enhancements. I will tell you in this blog what to do, so you can use the same function in your system.

Modifications in 5 places in the code are needed.

First, we need to add a selection parameter for the new Option.

I inserted this after the block with the search string (a05), after line 28 (in the unmodified source).

                  BEGIN OF BLOCK zz1 WITH FRAME TITLE TEXT-zz1.
SELECTION-SCREEN: BEGIN OF LINE.
PARAMETERS:       p_zz_mod TYPE xfeld AS CHECKBOX.
SELECTION-SCREEN: COMMENT (79) TEXT-zz3.
SELECTION-SCREEN: END OF LINE.
SELECTION-SCREEN: END OF BLOCK zz1,

Of course, you also need to add the texts. I use the following:

  • p_zz_mod: DUMMY – see text-zz3
  • text-zz1: Special Options
  • text-zz3: Also search all modif., and enhancements for selected packages (default Z* Y*)

Next is the definition of the local class that contains the logic.

I put this behind SAPs definition of local class lcl_source_scan (line 229 in the unmodified source):

CLASS lcl_zz DEFINITION FINAL.
  PUBLIC SECTION.
    TYPES: BEGIN OF tp_dynpro, " needs to be identical to ty_dynpro in lcl_source_scan
             repname LIKE d020s-prog,
             dynnr   LIKE d020s-dnum,
           END OF tp_dynpro.
    TYPES: ttp_dynpro TYPE STANDARD TABLE OF tp_dynpro WITH DEFAULT KEY.
    CLASS-METHODS:
      add_modification_includes
        CHANGING c_includes TYPE prognames
                 c_screens  TYPE ttp_dynpro
    , add_enhancement_includes
        CHANGING c_includes TYPE prognames
    , filter_modifications
        IMPORTING i_include     TYPE progname
                  i_screen      TYPE sy-dynnr
                  i_source_tab   TYPE abaptxt255_tab
        CHANGING  c_findings TYPE match_result_tab
    .
  PRIVATE SECTION.
    TYPES: BEGIN OF tp_mod_include
         , include TYPE reposrc-progname
         , without_assistant TYPE abap_bool
         , END OF tp_mod_include
         , ttp_mod_includes TYPE STANDARD TABLE OF tp_mod_include WITH KEY include

         , BEGIN OF tp_mod_screen
         ,   program TYPE d020s-prog
         ,   screen  TYPE d020s-dnum
         ,   without_assistant TYPE abap_bool
         , END OF tp_mod_screen
         , ttp_mod_screens TYPE STANDARD TABLE OF tp_mod_screen WITH KEY program
         .
    CLASS-METHODS:
      get_method_include
        IMPORTING i_class        TYPE seoclsname
                  i_method       TYPE seocpdname
        RETURNING VALUE(r_include) TYPE progname
    , get_function_include
        IMPORTING i_function     TYPE tfdir-funcname
        RETURNING VALUE(r_include) TYPE progname
    , get_all_includes_and_screens
        IMPORTING i_type TYPE smodilog-obj_type
                  i_name TYPE smodilog-obj_name
        EXPORTING e_includes TYPE prognames
                  e_screens TYPE ttp_mod_screens
    , get_screen_flow_logic
        IMPORTING i_screen TYPE tp_mod_screen
        RETURNING VALUE(r_source_tab) TYPE d022s_t
    .
    CLASS-DATA:
      s_mod_includes TYPE ttp_mod_includes
    , s_mod_screens  TYPE ttp_mod_screens
    .
ENDCLASS.

Because I wanted to modify the report in a “minimally invasive” way, the additional logic is inserted in two places:

  • Where the includes to be searched are collected
  • And then, after an include has been searched, to filter out the search results that occur in the unmodified parts of the code.

The next position to modify code is for this filtering. It is in method search_source, after the two FIND ALL statements, before the CHECK lt_results IS NOT INITIAL (in the unmodified report, line 946).

Code to be inserted:

        IF p_zz_mod = abap_true AND lt_results IS NOT INITIAL.
          lcl_zz=>filter_modifications( EXPORTING i_include     = gv_report
                                                  i_screen      = gv_dynpro
                                                  i_source_tab   = gt_source
                                        CHANGING  c_findings = lt_results ).
        ENDIF.

And then, at the end of method get_source_names (line 1189 in the unmodified code):

    IF p_zz_mod = abap_true.
      lcl_zz=>add_enhancement_includes( CHANGING c_includes = gt_object ).
      lcl_zz=>add_modification_includes( CHANGING c_includes = gt_object
                                                  c_screens  = gt_dynpro ).
    ENDIF.

Finally, at the end of the report, the class implementation:

CLASS lcl_zz IMPLEMENTATION.
  METHOD add_enhancement_includes.
    DATA devclass_range TYPE RANGE OF tadir-devclass.
    IF devclass IS INITIAL. " if no devclass restriction on selection screen, assume the user only wants the customer (Z and Y) enhancements
      devclass_range = VALUE #( sign = 'I' option = 'CP' ( low = 'Z*' ) ( low = 'Y*' ) ).
    ELSE.
      devclass_range = devclass[].
    ENDIF.
    SELECT obj_name
      FROM tadir
      WHERE pgmid  = 'R3TR'
      AND   object = 'ENHO'
      AND   devclass IN @devclass_range
      AND   delflag = ''
      INTO TABLE @data(enhancements).
    LOOP AT enhancements INTO DATA(enh).
      TRANSLATE enh(30) USING ' ='.
      enh+30 = 'E'.
      APPEND enh TO c_includes.
    ENDLOOP.
  ENDMETHOD.

  METHOD add_modification_includes.
    DATA include TYPE progname.
    CLEAR: s_mod_includes, s_mod_screens.
    IF sy-batch IS INITIAL.
      CALL FUNCTION 'SAPGUI_PROGRESS_INDICATOR'
        EXPORTING
          text = 'GET MODIFICATION INCLUDES...'.
    ENDIF.
    SELECT DISTINCT obj_type, obj_name, sub_type, sub_name, operation, prot_only AS without_assistant
      FROM smodilog
      WHERE NOT operation IN ('MIGR', 'IMPL', 'TRSL', 'NOTE')  "in sync with SE95 and Clone Finder
          AND inactive = ''
          AND int_type NOT IN ('DUMY') "clone finder also ignores 'XXXX'=without modification assistant, but we think this is wrong
          AND obj_type IN ('PROG', 'LDBA',          " LDBA = logical database, similar to PROG
                           'FUGR', 'FUGX', 'FUGS',  " FUGX and FUGS = parts of function groups for user exits
                           'CLAS')
          AND sub_type NOT IN ( 'REPT', 'FUGT', 'CUAD', 'DOCU', 'VARI' )
      ORDER BY obj_type, obj_name, sub_type, sub_name, prot_only
      INTO TABLE @DATA(modifications).

    LOOP AT modifications REFERENCE INTO DATA(mod).
      CASE mod->sub_type.
        WHEN 'REPS'. " report source
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = mod->sub_name
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'METH'.
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = get_method_include( i_class  = EXACT #( mod->obj_name )
                                                                             i_method = EXACT #( mod->sub_name+30 ) )
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'FUNC'.
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = get_function_include( i_function = EXACT #( mod->sub_name ) )
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'LDBA'. " logical databases. Seem to have these two code objects
          s_mod_includes = VALUE #( BASE s_mod_includes
                                    ( include           = mod->sub_name && 'SEL'
                                      without_assistant = mod->without_assistant )
                                    ( include           = 'SAP' && mod->sub_name
                                      without_assistant = mod->without_assistant ) ).
        WHEN 'CLAS'. " complete class
          cl_oo_classname_service=>get_all_class_includes( EXPORTING class_name = EXACT #( mod->obj_name )
                                                           RECEIVING result     = DATA(class_includes)
                                                           EXCEPTIONS OTHERS    = 0 ).
          DELETE class_includes WHERE table_line+30(2) = 'CS' OR table_line+30(2) = 'CP'.  " CS = complete source, CP = frameprogram for class pool
          s_mod_includes = VALUE #( BASE s_mod_includes
                                    FOR class_incl IN class_includes
                                    ( include           = class_incl
                                      without_assistant = mod->without_assistant ) ).
        WHEN 'CINC'. " class include
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = EXACT #( mod->sub_name )
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'CPUB' OR 'CPRO' OR 'CPRI'. " class public/protected/private definitions
          include = mod->sub_name.
          TRANSLATE include USING ' ='.
          include+30 =  SWITCH #( mod->sub_type WHEN 'CPUB' THEN 'CU'
                                                WHEN 'CPRO' THEN 'CO'
                                                WHEN 'CPRI' THEN 'CI' ).
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = include
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'FUGR' OR 'PROG'. " complete report or function group
          get_all_includes_and_screens( EXPORTING i_type = mod->sub_type
                                                  i_name = EXACT #( mod->sub_name )
                                        IMPORTING e_includes = DATA(includes)
                                                  e_screens  = DATA(screens) ).
          s_mod_includes = VALUE #( BASE s_mod_includes
                                    FOR incl IN includes
                                    ( include           = incl
                                      without_assistant = mod->without_assistant ) ).
          s_mod_screens = VALUE #( BASE s_mod_screens
                                   FOR scr IN screens
                                   ( program           = scr-program
                                     screen            = scr-screen
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'DYNP'. " screen (dynpro) - the program only searches through the flow logic (code)
          s_mod_screens = VALUE #( BASE s_mod_screens
                                   ( program           = mod->sub_name(40)
                                     screen            = mod->sub_name+40(4)
                                     without_assistant = mod->without_assistant ) ).
        WHEN OTHERS.
          LOG-POINT ID zlog FIELDS mod->obj_type mod->obj_name mod->sub_type mod->sub_name. " unknown subtype of modification
      ENDCASE.
    ENDLOOP.

    " pass result to caller, but also keep our own data, to be used later for filtering
    SORT s_mod_includes BY include.                 " for binary search later
    DELETE ADJACENT DUPLICATES FROM s_mod_includes.
    SORT s_mod_screens BY program screen.           " for binary search later
    DELETE ADJACENT DUPLICATES FROM s_mod_screens COMPARING program screen.  " there are duplicates here because of the way SMODILOG is used
    c_includes = VALUE #( BASE c_includes
                             FOR incl_ IN s_mod_includes
                             ( incl_-include ) ).
    c_screens = VALUE #( BASE c_screens
                             FOR scr_ IN s_mod_screens
                             ( repname = scr_-program
                               dynnr   = scr_-screen ) ).
  ENDMETHOD.

  METHOD get_method_include.
    DATA: generic_instance TYPE REF TO if_oo_clif_incl_naming
          , class_instance TYPE REF TO if_oo_class_incl_naming.
    cl_oo_include_naming=>get_instance_by_name( EXPORTING  name           = i_class
                                                RECEIVING  cifref         = generic_instance
                                                EXCEPTIONS no_objecttype  = 1
                                                           internal_error = 2
                                                           OTHERS         = 3 ).
    class_instance ?= generic_instance.
    class_instance->get_include_by_mtdname( EXPORTING  mtdname                      = i_method
                                                       with_enhancements            = abap_true
                                                       with_alias_resolution        = abap_true
                                            RECEIVING  progname                     = r_include
                                            EXCEPTIONS internal_method_not_existing = 1
                                                       OTHERS                       = 2 ).
    ASSERT sy-subrc = 0. " method not found
  ENDMETHOD.

  METHOD get_function_include.
    SELECT SINGLE pname
      FROM tfdir
      WHERE funcname = @i_function
      INTO @r_include.
    ASSERT sy-subrc = 0.
  ENDMETHOD.

  METHOD get_all_includes_and_screens.
    IF i_type = 'PROG'.
      DATA(frame_program) = i_name.
    ELSE.
      DATA(function_group) = EXACT rs38l_area( i_name ).
      CALL FUNCTION 'FUNCTION_INCLUDE_CONCATENATE'
        CHANGING
          program       = frame_program
          complete_area = function_group
        EXCEPTIONS
          OTHERS        = 1.
      ASSERT sy-subrc = 0.
    ENDIF.

    CALL FUNCTION 'RS_GET_ALL_INCLUDES'
      EXPORTING
        program    = frame_program
      TABLES
        includetab = e_includes
      EXCEPTIONS
        OTHERS     = 0.
    DELETE e_includes WHERE table_line CP 'LSVIM*'.  "remove reused includes from maintenance views
    SORT e_includes.
    DELETE ADJACENT DUPLICATES FROM e_includes.

    SELECT prog AS program, dnum AS screen
      FROM d020s
      WHERE prog = @frame_program
      INTO CORRESPONDING FIELDS OF TABLE @e_screens.
  ENDMETHOD.

  METHOD get_screen_flow_logic.
    DATA: header TYPE d020s
        , fields TYPE STANDARD TABLE OF d021s
        , parameters TYPE STANDARD TABLE OF d023s
        , BEGIN OF screen
        ,   prog TYPE d020s-prog
        ,   dnum TYPE d020s-dnum
        , END OF screen
        .
    screen = VALUE #( prog = i_screen-program
                      dnum = i_screen-screen ).
    IMPORT DYNPRO header fields r_source_tab parameters ID screen.
  ENDMETHOD.

  METHOD filter_modifications.
    DATA in_modified_section TYPE abap_bool.

    IF i_screen IS INITIAL.
      READ TABLE s_mod_includes WITH KEY include = i_include REFERENCE INTO DATA(mod_incl) BINARY SEARCH.
      CHECK sy-subrc = 0.                             " do not filter findings of unmodified code
      CHECK mod_incl->without_assistant = abap_false. " do not filter findings of code modified without assistant
    ELSE.
      READ TABLE s_mod_screens WITH KEY program = i_include screen = i_screen REFERENCE INTO DATA(mod_scr) BINARY SEARCH.
      CHECK sy-subrc = 0.                             " do not filter findings of unmodified code
      CHECK mod_scr->without_assistant = abap_false.  " do not filter findings of code modified without assistant
    ENDIF.

    DATA(next_finding_index) = 1.
    READ TABLE c_findings INDEX 1 REFERENCE INTO DATA(next_finding).
    LOOP AT i_source_tab REFERENCE INTO DATA(source).
      DATA(source_index) = sy-tabix.
      IF source->*(2) = '*{'.
        IF in_modified_section = abap_true.
          LOG-POINT ID zlog FIELDS i_include i_screen. " modified include with irregular *{ *} pattern - can happen when modified code is copied into a modification
          RETURN.                                      " the best way can do is to leave the remaining source code findings unfiltered
        ELSE.
          in_modified_section = abap_true.
        ENDIF.
      ELSEIF source->*(2) = '*}'.
        IF in_modified_section = abap_false.
          LOG-POINT ID zlog FIELDS i_include i_screen. " modified include with irregular *{ *} pattern - can happen when modified code is copied into a modification
          RETURN.                                      " the best way can do is to leave the remaining source code findings unfiltered
        ELSE.
          in_modified_section = abap_false.
        ENDIF.
      ENDIF.
      DO.  " loop necessary, because there can be multiple findings for same source line
        IF source_index <> next_finding->line.
          EXIT.
        ENDIF.
        IF in_modified_section = abap_true.
          ADD 1 TO next_finding_index.                   " leave current, continue with next
        ELSE.
          DELETE c_findings INDEX next_finding_index. " delete current, continue with next
        ENDIF.
        READ TABLE c_findings INDEX next_finding_index REFERENCE INTO next_finding.
        IF sy-subrc <> 0.
          RETURN.
        ENDIF.
      ENDDO.
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

There are 3 statements “LOG-POINT ID zlog …” in the local class implementation. My intention is to have a technical information channel for “strange situations”. For this to work, you need to create a checkpoint group zlog in transaction SAAB (or replace zlog with your own, existing checkpoint group).

Alternatively, you could replace these with an ASSERT, or with a CHECK, or a MESSAGE statement(depending on the context in which the report is used in your company, and your preferred error handling style).

Implementation Notes:

  • Of course, instead of modifying the SAP report, you could also copy and adapt it (cloning). You could even do that as a local object ($TMP) in the development system, to avoid possible overhead. In general though, I would prefer modification over cloning, because cloning comes with potential future cost/effort/risk (if SAP improves or bugfixes the standard code in future releases, and the clone code stays behind, uninformed).
  • We have NetWeaver release 7.50, and I also use the new language features. If you have an older release, you should be able to adapt the Code, so it works with older versions of RS_ABAP_SOURCE_SCAN and ABAP.
  • I also modified the sequence of the radio buttons, putting rb_all first, so this (search ABAP and screen flow logic) is the default

Usage Notes:

  • If no package selection is given, enhancements are searched in all packages Z* and Y.
  • For modifications, the program tries to search only the modified sections. However, if the modification assistant is switched off, or if there are irregular *{ patterns (through copying of modified code), the complete text is searched.
  • Disclaimer: As usual, I cannot guarantee that the code is 100% bug-free.

 

Update 19.02.2020: I recently discovered that someone called “sap-russia” copied my code and cloned the SAP standard code to provide a “ready-to-use” Z-program for code search (without asking me). It is available via ABAPgit  or copy&paste here: https://github.com/sap-russia/ZRS_ABAP_SOURCE_SCAN

Assigned Tags

      16 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Tomas Buryanek
      Tomas Buryanek

      Wow, that is great. I am using RS_ABAP_SOURCE_SCAN almost daily. Thank you for sharing!

      I will try to implement it when I get more time.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      I'd definitely amend the original. Clones cause so many problems - although RS_ABAP_SOURCE_SCAN ever changing seems very unlikely!

      Author's profile photo Edo von Glan
      Edo von Glan
      Blog Post Author

      I share your opinion. I will edit the text to make that clear.

      Author's profile photo Thomas Fiedler
      Thomas Fiedler

      Hi Edo,

      with HANA you can also use the Source code search in Eclipse. See my blog post about it:

      https://blogs.sap.com/2014/01/23/abap-sourcecode-search/

       

      Regards,

      Thomas.

      Author's profile photo Edo von Glan
      Edo von Glan
      Blog Post Author

      thanks Thomas, I adapted the title and text

      Best regards, Edo

      Author's profile photo Edo von Glan
      Edo von Glan
      Blog Post Author

      Hi Thomas,

      we are now in the upgrade project to HANA, and I finally tested the Eclipse source code search.

      Wow!!! That is so fast!!! About 2 seconds to search through all our AND all SAP coding!!! (RS_ABAP_SOURCE_SCAN with my extensions takes about 5 Minutes for all our coding.)

      One feature wish, that would save me a lot of typing:

      A history box for "Search Filter", like there is for "Search Term".

      Because 98% of the time, I only want to search our coding, so I enter: package:z_draeger. But I do not like to type this again and again.

      I assume that most SAP customers (on HANA) have the same wish.

      Best regards,

      Edo

      Author's profile photo Florian Henninger
      Florian Henninger

      Nice one. Personally I do not use that often the code search and so I’m curious to know, what you guys are searching with it. Do you have some common Comments to get all on one screen or what do I need to imagine at that moment?

      ~florian

      Author's profile photo Edo von Glan
      Edo von Glan
      Blog Post Author

      Hi Florian,

      we have legacy implementations of (SAP-)customer-specific logic, that are distributed over multiple places (Z reports, User Exits, Modifications). Usually this involves a specific criterium, for example a specific order reason (VBAK-AUGRU). So the related coding will have IF or CASE statements with the order reason e.g. 'Z16' (or a corresponding constant).

      In this context, it is really important to search the modifications and enhancements as well, not just the "normal" Z coding.

      I think the code search is most often used in analysis, when you are maintaining or cleaning up legacy code (that is not perfectly documented).

      Best regards, Edo

      Author's profile photo Jonathan Bourne
      Jonathan Bourne

      Hi Edo,

      Good work on the enhancement to the search scan tool, I will give it a try.

      One approach that I use is to link enhancements or modifications to a custom Checkpoint Group created in transaction SAAB (e.g. Z_MOD_FOR_Z16). This means adding one line to each enhancement / modification:

      BREAK-POINT ID z_mod_for_z16.

      or

      LOG-POINT ID z_mod_for_z16 FIELDS lv_some_variable.

      There are some benefits to this:

      1. Use the where-used list from transaction SAAB to find the enhancements / modifications.
      2. Related enhancements / modifications can be grouped under a single Checkpoint Group.
      3. The positive side effect is that you can make use of the Checkpoint Group functionality allowing easy debugging of the enhancements / modifications if BREAK-POINT is used or logging if using LOG-POINT.

      Regards,
      Jonathan

      Author's profile photo Edo von Glan
      Edo von Glan
      Blog Post Author

      Hi Jonathan,

      yes, that is a useful Approach with LOG- and BREAK-POINT ID in enhancements and modifications.

      I use ASSERT ID a lot with a checkpoint Group that dumps on the Quality/Test System, but only logs on the production System. In this way, I find out about "unexpected situations" (potential Bugs) quickly, but do not risk disruptions in the production System.

      Best regards,

      Edo

      Author's profile photo Heiko Olderdissen
      Heiko Olderdissen

      Hello Edo

      Great effort! Maybe SAP can provide enhancement points in the standard program for your modification or even upgrade the code with your development. I think this is useful for all customers.

      Cheers

      Heiko

      Author's profile photo Bärbel Winkler
      Bärbel Winkler

      Thanks for putting this together, Edo!

      I'll find out what my colleagues think about this modification and whether or not we'd actually be making regular use of the added feature. This in any case seems a lot less involved than enabling the switch framework Thomas mentions upthread and the high memory usage that entails - and in systems where such a search wouldn't really be executed anyway.

      Cheers

      Bärbel

       

      Author's profile photo trupti agarwal
      trupti agarwal

      Interesting!! Thanks for your lucid explanation.

      Author's profile photo Mehmet Dagnilak
      Mehmet Dagnilak

      Hi Edo. This enhancement to search is excellent! It really saved me from a lot of trouble. Thank you so much for your efforts.

      There is one little bug: If an include is found both in the standard search and in add_modification_includes method, it gets duplicated in the output. At the end of lcl_source_scan=>get_includes method, you should do a cleanup on gt_object table.

      Author's profile photo Edo von Glan
      Edo von Glan
      Blog Post Author

      Hi Mehmet,

      Glad you find it useful!

      I think you are right about the bug. My version of the code has evolved from the version published here (I added an ALV display option, and fields for usage information from SCMON), and in that newer version I remove duplicates at a later stage, in the findings table.

      It is better to remove them at the source, of course. But I do not want to adapt the version published here, because I do not want to retest. It's a minor bug, anyway.

      Author's profile photo John Moulding
      John Moulding

      Hi Edo,

      this is an excellent piece of work;  I wish I'd found it when it was first posted!

      And of course SAP have now done something similar (Notes 2927009 and 3137413),  although I don't know (yet) if their version is as useful as yours.

      More power to you.

      Best regards,

      John Moulding.