Skip to content

Commit 4875dcb

Browse files
committed
Ruby: generate overlay discard predicates
1 parent 4029b89 commit 4875dcb

File tree

4 files changed

+342
-2
lines changed

4 files changed

+342
-2
lines changed

ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
import codeql.Locations as L
77

8+
/** Holds if and only if the database is an overlay. */
9+
overlay[local]
10+
pragma[nomagic]
11+
private predicate isOverlay() { databaseMetadata("isOverlay", "true") }
12+
813
module Ruby {
914
/** The base class for all AST nodes */
1015
class AstNode extends @ruby_ast_node {
@@ -48,6 +53,36 @@ module Ruby {
4853
final override string getAPrimaryQlClass() { result = "ReservedWord" }
4954
}
5055

56+
/** Gets the file containing the given `node`. */
57+
overlay[local]
58+
pragma[nomagic]
59+
private @file getNodeFile(@ruby_ast_node node) {
60+
exists(@location_default loc | ruby_ast_node_location(node, loc) |
61+
locations_default(loc, result, _, _, _, _)
62+
)
63+
}
64+
65+
/** Holds if `file` was extracted as part of the overlay database. */
66+
overlay[local]
67+
pragma[nomagic]
68+
private predicate discardFile(@file file) {
69+
isOverlay() and exists(@ruby_ast_node node | file = getNodeFile(node))
70+
}
71+
72+
/** Holds if `node` is in the `file` and is part of the overlay base database. */
73+
overlay[local]
74+
pragma[nomagic]
75+
private predicate discardableAstNode(@file file, @ruby_ast_node node) {
76+
not isOverlay() and file = getNodeFile(node)
77+
}
78+
79+
/** Holds if `node` should be discarded, because it is part of the overlay base and is in a file that was also extracted as part of the overlay database. */
80+
overlay[discard_entity]
81+
pragma[nomagic]
82+
private predicate discardAstNode(@ruby_ast_node node) {
83+
exists(@file file | discardableAstNode(file, node) and discardFile(file))
84+
}
85+
5186
class UnderscoreArg extends @ruby_underscore_arg, AstNode { }
5287

5388
class UnderscoreCallOperator extends @ruby_underscore_call_operator, AstNode { }
@@ -1970,6 +2005,36 @@ module Erb {
19702005
final override string getAPrimaryQlClass() { result = "ReservedWord" }
19712006
}
19722007

2008+
/** Gets the file containing the given `node`. */
2009+
overlay[local]
2010+
pragma[nomagic]
2011+
private @file getNodeFile(@erb_ast_node node) {
2012+
exists(@location_default loc | erb_ast_node_location(node, loc) |
2013+
locations_default(loc, result, _, _, _, _)
2014+
)
2015+
}
2016+
2017+
/** Holds if `file` was extracted as part of the overlay database. */
2018+
overlay[local]
2019+
pragma[nomagic]
2020+
private predicate discardFile(@file file) {
2021+
isOverlay() and exists(@erb_ast_node node | file = getNodeFile(node))
2022+
}
2023+
2024+
/** Holds if `node` is in the `file` and is part of the overlay base database. */
2025+
overlay[local]
2026+
pragma[nomagic]
2027+
private predicate discardableAstNode(@file file, @erb_ast_node node) {
2028+
not isOverlay() and file = getNodeFile(node)
2029+
}
2030+
2031+
/** Holds if `node` should be discarded, because it is part of the overlay base and is in a file that was also extracted as part of the overlay database. */
2032+
overlay[discard_entity]
2033+
pragma[nomagic]
2034+
private predicate discardAstNode(@erb_ast_node node) {
2035+
exists(@file file | discardableAstNode(file, node) and discardFile(file))
2036+
}
2037+
19732038
/** A class representing `code` tokens. */
19742039
class Code extends @erb_token_code, Token {
19752040
/** Gets the name of the primary QL class for this element. */

shared/tree-sitter-extractor/src/generator/mod.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub fn generate(
1717
languages: Vec<language::Language>,
1818
dbscheme_path: PathBuf,
1919
ql_library_path: PathBuf,
20-
add_metadata_relation: bool,
20+
overlay_support: bool,
2121
) -> std::io::Result<()> {
2222
let dbscheme_file = File::create(dbscheme_path).map_err(|e| {
2323
tracing::error!("Failed to create dbscheme file: {}", e);
@@ -35,7 +35,7 @@ pub fn generate(
3535

3636
// Eventually all languages will have the metadata relation (for overlay support), at which
3737
// point this could be moved to prefix.dbscheme.
38-
if add_metadata_relation {
38+
if overlay_support {
3939
writeln!(dbscheme_writer, "/*- Database metadata -*/",)?;
4040
dbscheme::write(
4141
&mut dbscheme_writer,
@@ -60,6 +60,15 @@ pub fn generate(
6060
})],
6161
)?;
6262

63+
if overlay_support {
64+
ql::write(
65+
&mut ql_writer,
66+
&[ql::TopLevel::Predicate(
67+
ql_gen::create_is_overlay_predicate(),
68+
)],
69+
)?;
70+
}
71+
6372
for language in languages {
6473
let prefix = node_types::to_snake_case(&language.name);
6574
let ast_node_name = format!("{}_ast_node", &prefix);
@@ -103,6 +112,22 @@ pub fn generate(
103112
ql::TopLevel::Class(ql_gen::create_token_class(&token_name, &tokeninfo_name)),
104113
ql::TopLevel::Class(ql_gen::create_reserved_word_class(&reserved_word_name)),
105114
];
115+
116+
if overlay_support {
117+
body.push(ql::TopLevel::Predicate(
118+
ql_gen::create_get_node_file_predicate(&ast_node_name, &node_location_table_name),
119+
));
120+
body.push(ql::TopLevel::Predicate(
121+
ql_gen::create_discard_file_predicate(&ast_node_name),
122+
));
123+
body.push(ql::TopLevel::Predicate(
124+
ql_gen::create_discardable_ast_node_predicate(&ast_node_name),
125+
));
126+
body.push(ql::TopLevel::Predicate(
127+
ql_gen::create_discard_ast_node_predicate(&ast_node_name),
128+
));
129+
}
130+
106131
body.append(&mut ql_gen::convert_nodes(&nodes));
107132
ql::write(
108133
&mut ql_writer,

shared/tree-sitter-extractor/src/generator/ql.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub enum TopLevel<'a> {
66
Class(Class<'a>),
77
Import(Import<'a>),
88
Module(Module<'a>),
9+
Predicate(Predicate<'a>),
910
}
1011

1112
impl fmt::Display for TopLevel<'_> {
@@ -14,6 +15,7 @@ impl fmt::Display for TopLevel<'_> {
1415
TopLevel::Import(imp) => write!(f, "{}", imp),
1516
TopLevel::Class(cls) => write!(f, "{}", cls),
1617
TopLevel::Module(m) => write!(f, "{}", m),
18+
TopLevel::Predicate(pred) => write!(f, "{}", pred),
1719
}
1820
}
1921
}
@@ -68,10 +70,13 @@ impl fmt::Display for Class<'_> {
6870
qldoc: None,
6971
name: self.name,
7072
overridden: false,
73+
is_private: false,
7174
is_final: false,
7275
return_type: None,
7376
formal_parameters: vec![],
7477
body: charpred.clone(),
78+
pragma: None,
79+
overlay: None,
7580
}
7681
)?;
7782
}
@@ -150,6 +155,7 @@ pub enum Expression<'a> {
150155
expr: Box<Expression<'a>>,
151156
second_expr: Option<Box<Expression<'a>>>,
152157
},
158+
Negation(Box<Expression<'a>>),
153159
}
154160

155161
impl fmt::Display for Expression<'_> {
@@ -231,26 +237,59 @@ impl fmt::Display for Expression<'_> {
231237
}
232238
write!(f, ")")
233239
}
240+
Expression::Negation(e) => write!(f, "not ({})", e),
234241
}
235242
}
236243
}
237244

245+
#[derive(Clone, Eq, PartialEq, Hash)]
246+
pub enum PragmaAnnotation {
247+
Nomagic,
248+
}
249+
250+
#[derive(Clone, Eq, PartialEq, Hash)]
251+
pub enum OverlayAnnotation {
252+
Local,
253+
DiscardEntity,
254+
}
255+
238256
#[derive(Clone, Eq, PartialEq, Hash)]
239257
pub struct Predicate<'a> {
240258
pub qldoc: Option<String>,
241259
pub name: &'a str,
242260
pub overridden: bool,
261+
pub is_private: bool,
243262
pub is_final: bool,
244263
pub return_type: Option<Type<'a>>,
245264
pub formal_parameters: Vec<FormalParameter<'a>>,
246265
pub body: Expression<'a>,
266+
pub pragma: Option<PragmaAnnotation>,
267+
pub overlay: Option<OverlayAnnotation>,
247268
}
248269

249270
impl fmt::Display for Predicate<'_> {
250271
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251272
if let Some(qldoc) = &self.qldoc {
252273
write!(f, "/** {} */", qldoc)?;
253274
}
275+
if let Some(overlay_annotation) = &self.overlay {
276+
write!(f, "overlay[")?;
277+
match overlay_annotation {
278+
OverlayAnnotation::Local => write!(f, "local")?,
279+
OverlayAnnotation::DiscardEntity => write!(f, "discard_entity")?,
280+
}
281+
write!(f, "] ")?;
282+
}
283+
if let Some(pragma) = &self.pragma {
284+
write!(f, "pragma[")?;
285+
match pragma {
286+
PragmaAnnotation::Nomagic => write!(f, "nomagic")?,
287+
}
288+
write!(f, "] ")?;
289+
}
290+
if self.is_private {
291+
write!(f, "private ")?;
292+
}
254293
if self.is_final {
255294
write!(f, "final ")?;
256295
}

0 commit comments

Comments
 (0)