Skip to content

Conversation

@AndreiDreyer
Copy link
Contributor

@AndreiDreyer AndreiDreyer commented Apr 23, 2025

  • Added support for ERB files
  • Added templateOutRaw (<%== %>) and templateOutEscape (<%= %>) operator calls
  • Added RETURN for the joern__buffer which holds all of the appended strings from the ERB lowering
  • Added .html.erb files as config files

@AndreiDreyer AndreiDreyer added the ruby Relates to rubysrc2cpg label Apr 23, 2025
@AndreiDreyer AndreiDreyer self-assigned this Apr 23, 2025
Copy link
Contributor

@DavidBakerEffendi DavidBakerEffendi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

@maltek
Copy link
Contributor

maltek commented Apr 24, 2025

I'm still missing a lot of .erb files. E.g. for https://github.com/chatwoot/chatwoot:

ocular> cpg.file.name(".*.html.erb").size
val res18: Int = 40

ocular> cpg.configFile.name(".*.html.erb").size
val res19: Int = 88

The frontend spits out some warnings for the project, e.g. Yield expression outside of method scope: yield(:head) and Type 'String' is considered a 'core' type, not a 'Kernel-contained' type - but not enough to explain 48 missing files.

@AndreiDreyer
Copy link
Contributor Author

@maltek these are the new results on chatwoot with all of the changes made for ERB handling.

joern> cpg.configFile.name(".*.html.erb").l.map(_.name).l.diff(cpg.file.name(".*.html.erb").map(_.name).l)
val res1: List[String] = List()

joern> cpg.file.name(".*.html.erb").l.size
val res2: Int = 88

joern> cpg.configFile.name(".*.html.erb").l.size
val res3: Int = 88

import io.shiftleft.codepropertygraph.generated.nodes.{Block, Call, FieldIdentifier, Identifier, Literal, TypeRef}
import io.shiftleft.semanticcpg.language.*

class ErbTests extends RubyCode2CpgFixture {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you writing tests that include the lowering of a ruby AST into CPG?
The ERB support takes input ERB files and emits ruby code that is then fed into rubyAstGen. For the later part we already have plenty of tests. Here it would be much better readable if you test the first step ( ERB -> ruby code) the added benefit would be much better readability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERB -> Ruby code is tested on the ruby_ast_gen project e.g., https://github.com/joernio/ruby_ast_gen/blob/main/spec/ruby_ast_gen_spec.rb#L155

Albeit, one could argue for more tests there perhaps, but we haven't designed the RubyAstGen hook to give us the lowered ERB code as-is, only via AST

@ml86 ml86 self-requested a review May 15, 2025 09:04
@maltek
Copy link
Contributor

maltek commented May 23, 2025

all the ruby code in
https://github.com/forem/forem/blob/0772f2d49b18d94f3b982b39420ea31235c1c8aa/app/views/layouts/application.html.erb#L88-L92 exists in the CPG only as Literal nodes, instead of the actual calls and control structures. Also the lines are off-by-one (code from line 90 has line 91 in the CPG).

@maltek
Copy link
Contributor

maltek commented Jun 16, 2025

@maltek
Copy link
Contributor

maltek commented Jun 17, 2025

the rails_lambda_0 calls are also problematic:

@maltek maltek force-pushed the andrei/ruby-erb-support branch from c408dfd to 233da30 Compare July 8, 2025 12:17
@maltek
Copy link
Contributor

maltek commented Aug 8, 2025

current problem:

class UsersController < ActionController::Base
  def show
    respond_to do |format|
      format.json { render partial: "foo" }
    end
  end
end

some self identifiers refer to multiple locals:

cpg.identifier.name("self").filter(_.refsTo.size != 1).l

Identifiers should only ever have REF edges to the single variable of that name in the exact same method.

@ml86 ml86 assigned TNSelahle and unassigned AndreiDreyer Aug 11, 2025
@maltek
Copy link
Contributor

maltek commented Sep 1, 2025

@TNSelahle the problem with the self variable is fixed with your last change. But there is still a related problem: the format identifiers are not referencing the format MethodParameterIn node.

@TNSelahle TNSelahle force-pushed the andrei/ruby-erb-support branch from 83462f3 to 47aa16b Compare September 11, 2025 13:42
@TNSelahle TNSelahle force-pushed the andrei/ruby-erb-support branch 2 times, most recently from ea86534 to b834c49 Compare October 14, 2025 11:04
@maltek maltek requested a review from ml86 October 16, 2025 12:41
Comment on lines 326 to 330
val typeFullName = if (node.memberName == "joern__buffer" || node.memberName == "joern__inner_buffer") {
Constants.stringPrefix
} else {
Defines.Any
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should handle this special case in the general astForFieldAccess method. Can we make the type an optional parameter that's handed down here from the surrounding context if it's known?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's sort of the same special case now, just one layer up. I thought we could do it from around here where we've already determined to be dealing with the ERB lowering. But I just read over the diff in the browser, so I could very well be mistaken.

val ast = astForFieldAccess(MemberAccess(n.target, ".", n.methodName)(n.span), stripLeadingAt = true)
(n.op, hasStringArgs, ast)

@TNSelahle
Copy link
Member

@maltek thanks for the review! I've made and pushed up the changes. Integration tests on CS master are all green.

Comment on lines 326 to 330
val typeFullName = if (node.memberName == "joern__buffer" || node.memberName == "joern__inner_buffer") {
Constants.stringPrefix
} else {
Defines.Any
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's sort of the same special case now, just one layer up. I thought we could do it from around here where we've already determined to be dealing with the ERB lowering. But I just read over the diff in the browser, so I could very well be mistaken.

val ast = astForFieldAccess(MemberAccess(n.target, ".", n.methodName)(n.span), stripLeadingAt = true)
(n.op, hasStringArgs, ast)

@TNSelahle
Copy link
Member

#5447 (comment)

@maltek the snippet you highlighted applies to astForFieldAccess for the self.joernBufferAppend call. The special case for you identified in the astForFieldAccess method was for field access for self.joernBuffer and self.joernInnerBuffer.

I'll rename isErbCall to isErbBufferApppendCall to make it clearer.

@TNSelahle TNSelahle force-pushed the andrei/ruby-erb-support branch from 39947b2 to f0fdf28 Compare October 27, 2025 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ruby Relates to rubysrc2cpg

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants