integrated-application-development / sonar-delphi

Delphi language plugin for SonarQube
GNU Lesser General Public License v3.0
104 stars 16 forks source link

Parsing error on parenthesised anonymous procedure #226

Closed zaneduffield closed 5 months ago

zaneduffield commented 6 months ago

Prerequisites

SonarDelphi version

1.4.0

SonarQube version

No response

Issue description

When an anonymous procedure literal is called directly, sonar-delphi fails to parse it.

Steps to reproduce

Scan the following delphi program

program Example;
begin
  (procedure(a: String) begin end)('');
end.

and observe the error

au.com.integradev.delphi.file.DelphiFile$DelphiFileConstructionException: Failed to construct DelphiFile (line 4:3 no viable alternative at input 'procedure')
    at au.com.integradev.delphi.file.DelphiFile.setupFile(DelphiFile.java:159)
    at au.com.integradev.delphi.file.DelphiFile$DelphiInputFile.from(DelphiFile.java:75)
    at au.com.integradev.delphi.DelphiSensor.executeOnFiles(DelphiSensor.java:143)
    at au.com.integradev.delphi.DelphiSensor.execute(DelphiSensor.java:83)
    at org.sonarsource.sonarlint.core.analysis.container.analysis.sensor.SensorsExecutor.executeSensor(SensorsExecutor.java:75)
    at org.sonarsource.sonarlint.core.analysis.container.analysis.sensor.SensorsExecutor.execute(SensorsExecutor.java:66)
    at org.sonarsource.sonarlint.core.analysis.container.analysis.AnalysisContainer.doAfterStart(AnalysisContainer.java:122)
    at org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer.startComponents(SpringComponentContainer.java:182)
    at org.sonarsource.sonarlint.core.plugin.commons.container.SpringComponentContainer.execute(SpringComponentContainer.java:161)
    at org.sonarsource.sonarlint.core.analysis.container.module.ModuleContainer.analyze(ModuleContainer.java:71)
    at au.com.integradev.delphilint.analysis.AnalysisOrchestrator.runAnalysis(AnalysisOrchestrator.java:147)
    at au.com.integradev.delphilint.server.AnalysisServer.analyze(AnalysisServer.java:115)
    at au.com.integradev.delphilint.server.TlvConnection.processRequest(TlvConnection.java:171)
    at au.com.integradev.delphilint.server.TlvConnection.readStream(TlvConnection.java:158)
    at au.com.integradev.delphilint.server.TlvConnection.run(TlvConnection.java:70)
    at au.com.integradev.delphilint.App.main(App.java:70)
Caused by: au.com.integradev.delphi.antlr.DelphiParser$ParserException: line 4:3 no viable alternative at input 'procedure'
    at au.com.integradev.delphi.antlr.DelphiParser.reportError(DelphiParser.java:386)
    at au.com.integradev.delphi.antlr.DelphiParser.statement(DelphiParser.java:25090)
    at au.com.integradev.delphi.antlr.DelphiParser.delimitedStatements(DelphiParser.java:29460)
    at au.com.integradev.delphi.antlr.DelphiParser.statementList(DelphiParser.java:29345)
    at au.com.integradev.delphi.antlr.DelphiParser.compoundStatement(DelphiParser.java:29273)
    at au.com.integradev.delphi.antlr.DelphiParser.programBody(DelphiParser.java:1121)
    at au.com.integradev.delphi.antlr.DelphiParser.program(DelphiParser.java:796)
    at au.com.integradev.delphi.antlr.DelphiParser.file(DelphiParser.java:495)
    at au.com.integradev.delphi.file.DelphiFile.createAST(DelphiFile.java:194)
    at au.com.integradev.delphi.file.DelphiFile.setupFile(DelphiFile.java:147)
    ... 15 more
Caused by: NoViableAltException(129@[])
    at au.com.integradev.delphi.antlr.DelphiParser.statement(DelphiParser.java:24809)
    ... 23 more

Minimal Delphi code exhibiting the issue

No response

zaneduffield commented 6 months ago

I should note that this example program does compile for me, with Delphi 11.2.

zaneduffield commented 6 months ago

Also worth noting is the fact that the example doesn't compile if the parentheses are removed:

program Example;
begin
  procedure(a: String) begin end('');
end.
zaneduffield commented 6 months ago

Also, this issue is still present when the procedure isn't called:

begin
  var proc := (procedure begin end);
end.
zaneduffield commented 6 months ago

It seems that the expression parsing needs a little tweaking

When an assignment expression is parsed, it specifically allows an anonymous method to be the RHS: https://github.com/integrated-application-development/sonar-delphi/blob/bd8a080a2f27e19852c76c5a7b5255e8c2d06781/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g#L884 and similarly for inline var https://github.com/integrated-application-development/sonar-delphi/blob/bd8a080a2f27e19852c76c5a7b5255e8c2d06781/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g#L851

However, when parsing parenthesised expressions it just uses the plain expression fragment: https://github.com/integrated-application-development/sonar-delphi/blob/bd8a080a2f27e19852c76c5a7b5255e8c2d06781/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g#L719

I don't know if the best way to fix this is to allow an anonymous procedure to be a first-class expression, or if we just need to add a special case for parenthesised expressions to include anonymous procedures.

Cirras commented 6 months ago

Based on your example, we should probably promote anonymous methods to proper expressions in the grammar.