Search code examples
sonarqubexpath-1.0

XPath-1.0: mark node where opposite node is missing


I try to write custom rule in sonar (plsql) by copy xpath rule. The task of this rule is to mark 'rollback to savepoint' statement where savepoint statement is missing.

For 2 savepoint and 3 rollback statements the AST (Abstract Syntax Tree) looks like:

<PROCEDURE_DEFINITION>
  ...
  <SAVEPOINT>
    <IDENTIFIER tokenValue="SAVEPOINT" />
    <IDENTIFIER tokenValue="spA" />
    <SEMICOLON tokenValue=";" />
  </SAVEPOINT>
  ...
  <SAVEPOINT>
    <IDENTIFIER tokenValue="SAVEPOINT" />
    <IDENTIFIER tokenValue="spB" />
    <SEMICOLON tokenValue=";" />
  </SAVEPOINT>
  ...
  <ROLLBACK>
    <IDENTIFIER tokenValue="ROLLBACK" />
    <TO />
    <IDENTIFIER tokenValue="SAVEPOINT" />
    <IDENTIFIER tokenValue="spB" />
    <SEMICOLON tokenValue=";" />
  </ROLLBACK>
  ...
  <ROLLBACK>
    <IDENTIFIER tokenValue="ROLLBACK" />
    <TO />
    <IDENTIFIER tokenValue="SAVEPOINT" />
    <IDENTIFIER tokenValue="spA" />
    <SEMICOLON tokenValue=";" />
  </ROLLBACK>
  ...
  <ROLLBACK>
    <IDENTIFIER tokenValue="ROLLBACK" />
    <TO />
    <IDENTIFIER tokenValue="SAVEPOINT" />
    <IDENTIFIER tokenValue="spX" />
    <SEMICOLON tokenValue=";" />
  </ROLLBACK>
  ...
</PROCEDURE_DEFINITION> 

I search for XPath query that marks last rollback because savepoint spX is missing. But this xpath marks last both rollbacks

//PROCEDURE_DEFINITION//ROLLBACK[
    IDENTIFIER[
        @tokenValue =
            ./ancestor::PROCEDURE_DEFINITION
                //SAVEPOINT/IDENTIFIER[2]/@tokenValue
    ]
]

Any suggestions?

EDIT:
I found this suboptimal solution:

//PROCEDURE_DEFINITION//ROLLBACK
[
  not(
    ./IDENTIFIER[3]/@tokenValue = 
    ancestor::PROCEDURE_DEFINITION//SAVEPOINT/IDENTIFIER[2]/@tokenValue
  )
]

PLSQL is case insensitive. But when i add translate function, i get marked first and last ROLLBACK node. I think all rollback's names will agreed with first savepoint's name?


Solution

  • First of all, according to the AST you show, it seems to me that this is the PL/SQL code you want to run your XPath query on:

    DECLARE
      PROCEDURE foo AS
      BEGIN
        SAVEPOINT spA;
        SAVEPOINT spB;
    
        ROLLBACK spB; -- Compliant
        ROLLBACK spA; -- Compliant
    
        ROLLBACK spX; -- Non-Compliant
      END;
    BEGIN
      NULL;
    END;
    /
    

    When I run your first XPath query using the SSLR PL/SQL Toolkit, the rollbacks to 'spB' and 'spA' are selected, but not the one to 'spX'.

    Your second XPath query selects all of them.

    It seems to me that you only want to select the one to 'spX', as there is no corresponding savepoint.

    A trivial change to your first query allows to reverse the selected nodes, by negating the condition using not():

    //PROCEDURE_DEFINITION//ROLLBACK[
        not(IDENTIFIER[
            @tokenValue =
                ./ancestor::PROCEDURE_DEFINITION
                    //SAVEPOINT/IDENTIFIER[2]/@tokenValue
        ])
    ]
    

    But I would actually recommend that you drop the PROCEDURE_DEFINITION part of the query, as SAVEPOINT and ROLLBACK statements are also valid within functions or anonymous blocks:

    //ROLLBACK[not(IDENTIFIER[@tokenValue = //SAVEPOINT/IDENTIFIER[2]/@tokenValue])]