mirror of
https://github.com/github/codeql.git
synced 2026-04-28 10:15:14 +02:00
Merge remote-tracking branch 'origin/main' into nickrolfe/oj
This commit is contained in:
@@ -26,12 +26,12 @@ fn main() -> std::io::Result<()> {
|
||||
|
||||
for line in env::var("LGTM_INDEX_FILTERS")
|
||||
.unwrap_or_default()
|
||||
.split("\n")
|
||||
.split('\n')
|
||||
{
|
||||
if line.starts_with("include:") {
|
||||
cmd.arg("--include").arg(&line[8..]);
|
||||
} else if line.starts_with("exclude:") {
|
||||
cmd.arg("--exclude").arg(&line[8..]);
|
||||
if let Some(stripped) = line.strip_prefix("include:") {
|
||||
cmd.arg("--include").arg(stripped);
|
||||
} else if let Some(stripped) = line.strip_prefix("exclude:") {
|
||||
cmd.arg("--exclude").arg(stripped);
|
||||
}
|
||||
}
|
||||
let exit = &cmd.spawn()?.wait()?;
|
||||
|
||||
@@ -165,7 +165,7 @@ pub fn extract(
|
||||
schema: &NodeTypeMap,
|
||||
trap_writer: &mut TrapWriter,
|
||||
path: &Path,
|
||||
source: &Vec<u8>,
|
||||
source: &[u8],
|
||||
ranges: &[Range],
|
||||
) -> std::io::Result<()> {
|
||||
let span = span!(
|
||||
@@ -180,17 +180,16 @@ pub fn extract(
|
||||
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(language).unwrap();
|
||||
parser.set_included_ranges(&ranges).unwrap();
|
||||
parser.set_included_ranges(ranges).unwrap();
|
||||
let tree = parser.parse(&source, None).expect("Failed to parse file");
|
||||
trap_writer.comment(format!("Auto-generated TRAP file for {}", path.display()));
|
||||
let file_label = &trap_writer.populate_file(path);
|
||||
let mut visitor = Visitor {
|
||||
source: &source,
|
||||
trap_writer: trap_writer,
|
||||
source,
|
||||
trap_writer,
|
||||
// TODO: should we handle path strings that are not valid UTF8 better?
|
||||
path: format!("{}", path.display()),
|
||||
file_label: *file_label,
|
||||
token_counter: 0,
|
||||
toplevel_child_counter: 0,
|
||||
stack: Vec::new(),
|
||||
language_prefix,
|
||||
@@ -206,15 +205,7 @@ pub fn extract(
|
||||
/// HTML entities.
|
||||
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
|
||||
fn needs_escaping(c: char) -> bool {
|
||||
match c {
|
||||
'&' => true,
|
||||
'{' => true,
|
||||
'}' => true,
|
||||
'"' => true,
|
||||
'@' => true,
|
||||
'#' => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
|
||||
}
|
||||
|
||||
let key = key.into();
|
||||
@@ -297,11 +288,9 @@ struct Visitor<'a> {
|
||||
/// source file.
|
||||
file_label: Label,
|
||||
/// The source code as a UTF-8 byte array
|
||||
source: &'a Vec<u8>,
|
||||
source: &'a [u8],
|
||||
/// A TrapWriter to accumulate trap entries
|
||||
trap_writer: &'a mut TrapWriter,
|
||||
/// A counter for tokens
|
||||
token_counter: usize,
|
||||
/// A counter for top-level child nodes
|
||||
toplevel_child_counter: usize,
|
||||
/// Language prefix
|
||||
@@ -345,7 +334,7 @@ impl Visitor<'_> {
|
||||
full_error_message: String,
|
||||
node: Node,
|
||||
) {
|
||||
let (start_line, start_column, end_line, end_column) = location_for(&self.source, node);
|
||||
let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
|
||||
let loc = self.trap_writer.location(
|
||||
self.file_label,
|
||||
start_line,
|
||||
@@ -376,7 +365,7 @@ impl Visitor<'_> {
|
||||
let id = self.trap_writer.fresh_id();
|
||||
|
||||
self.stack.push((id, 0, Vec::new()));
|
||||
return true;
|
||||
true
|
||||
}
|
||||
|
||||
fn leave_node(&mut self, field_name: Option<&'static str>, node: Node) {
|
||||
@@ -384,7 +373,7 @@ impl Visitor<'_> {
|
||||
return;
|
||||
}
|
||||
let (id, _, child_nodes) = self.stack.pop().expect("Vistor: empty stack");
|
||||
let (start_line, start_column, end_line, end_column) = location_for(&self.source, node);
|
||||
let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
|
||||
let loc = self.trap_writer.location(
|
||||
self.file_label,
|
||||
start_line,
|
||||
@@ -425,13 +414,10 @@ impl Visitor<'_> {
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Int(*kind_id),
|
||||
Arg::Label(self.file_label),
|
||||
Arg::Int(self.token_counter),
|
||||
sliced_source_arg(self.source, node),
|
||||
Arg::Label(loc),
|
||||
],
|
||||
);
|
||||
self.token_counter += 1;
|
||||
}
|
||||
EntryKind::Table {
|
||||
fields,
|
||||
@@ -446,11 +432,10 @@ impl Visitor<'_> {
|
||||
Arg::Int(parent_index),
|
||||
],
|
||||
);
|
||||
let mut all_args = Vec::new();
|
||||
all_args.push(Arg::Label(id));
|
||||
let mut all_args = vec![Arg::Label(id)];
|
||||
all_args.extend(args);
|
||||
all_args.push(Arg::Label(loc));
|
||||
self.trap_writer.add_tuple(&table_name, all_args);
|
||||
self.trap_writer.add_tuple(table_name, all_args);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -485,8 +470,8 @@ impl Visitor<'_> {
|
||||
fn complex_node(
|
||||
&mut self,
|
||||
node: &Node,
|
||||
fields: &Vec<Field>,
|
||||
child_nodes: &Vec<ChildNode>,
|
||||
fields: &[Field],
|
||||
child_nodes: &[ChildNode],
|
||||
parent_id: Label,
|
||||
) -> Option<Vec<Arg>> {
|
||||
let mut map: Map<&Option<String>, (&Field, Vec<Arg>)> = Map::new();
|
||||
@@ -523,22 +508,20 @@ impl Visitor<'_> {
|
||||
);
|
||||
self.record_parse_error_for_node(error_message, full_error_message, *node);
|
||||
}
|
||||
} else {
|
||||
if child_node.field_name.is_some() || child_node.type_name.named {
|
||||
let error_message = format!(
|
||||
"value for unknown field: {}::{} and type {:?}",
|
||||
node.kind(),
|
||||
&child_node.field_name.unwrap_or("child"),
|
||||
&child_node.type_name
|
||||
);
|
||||
let full_error_message = format!(
|
||||
"{}:{}: {}",
|
||||
&self.path,
|
||||
node.start_position().row + 1,
|
||||
error_message
|
||||
);
|
||||
self.record_parse_error_for_node(error_message, full_error_message, *node);
|
||||
}
|
||||
} else if child_node.field_name.is_some() || child_node.type_name.named {
|
||||
let error_message = format!(
|
||||
"value for unknown field: {}::{} and type {:?}",
|
||||
node.kind(),
|
||||
&child_node.field_name.unwrap_or("child"),
|
||||
&child_node.type_name
|
||||
);
|
||||
let full_error_message = format!(
|
||||
"{}:{}: {}",
|
||||
&self.path,
|
||||
node.start_position().row + 1,
|
||||
error_message
|
||||
);
|
||||
self.record_parse_error_for_node(error_message, full_error_message, *node);
|
||||
}
|
||||
}
|
||||
let mut args = Vec::new();
|
||||
@@ -586,13 +569,12 @@ impl Visitor<'_> {
|
||||
);
|
||||
break;
|
||||
}
|
||||
let mut args = Vec::new();
|
||||
args.push(Arg::Label(parent_id));
|
||||
let mut args = vec![Arg::Label(parent_id)];
|
||||
if *has_index {
|
||||
args.push(Arg::Int(index))
|
||||
}
|
||||
args.push(child_value.clone());
|
||||
self.trap_writer.add_tuple(&table_name, args);
|
||||
self.trap_writer.add_tuple(table_name, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -610,13 +592,10 @@ impl Visitor<'_> {
|
||||
if tp == single_type {
|
||||
return true;
|
||||
}
|
||||
match &self.schema.get(single_type).unwrap().kind {
|
||||
EntryKind::Union { members } => {
|
||||
if self.type_matches_set(tp, members) {
|
||||
return true;
|
||||
}
|
||||
if let EntryKind::Union { members } = &self.schema.get(single_type).unwrap().kind {
|
||||
if self.type_matches_set(tp, members) {
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
node_types::FieldTypeInfo::Multiple { types, .. } => {
|
||||
@@ -646,7 +625,7 @@ impl Visitor<'_> {
|
||||
}
|
||||
|
||||
// Emit a slice of a source file as an Arg.
|
||||
fn sliced_source_arg(source: &Vec<u8>, n: Node) -> Arg {
|
||||
fn sliced_source_arg(source: &[u8], n: Node) -> Arg {
|
||||
let range = n.byte_range();
|
||||
Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned())
|
||||
}
|
||||
@@ -654,7 +633,7 @@ fn sliced_source_arg(source: &Vec<u8>, n: Node) -> Arg {
|
||||
// Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated.
|
||||
// The first is the location and label definition, and the second is the
|
||||
// 'Located' entry.
|
||||
fn location_for<'a>(source: &Vec<u8>, n: Node) -> (usize, usize, usize, usize) {
|
||||
fn location_for(source: &[u8], n: Node) -> (usize, usize, usize, usize) {
|
||||
// Tree-sitter row, column values are 0-based while CodeQL starts
|
||||
// counting at 1. In addition Tree-sitter's row and column for the
|
||||
// end position are exclusive while CodeQL's end positions are inclusive.
|
||||
@@ -812,18 +791,18 @@ impl fmt::Display for Arg {
|
||||
/// the string is sliced at the provided limit. If there is a multi-byte character
|
||||
/// at the limit then the returned slice will be slightly shorter than the limit to
|
||||
/// avoid splitting that multi-byte character.
|
||||
fn limit_string(string: &String, max_size: usize) -> &str {
|
||||
fn limit_string(string: &str, max_size: usize) -> &str {
|
||||
if string.len() <= max_size {
|
||||
return string;
|
||||
}
|
||||
let p = string.as_ptr();
|
||||
let p = string.as_bytes();
|
||||
let mut index = max_size;
|
||||
// We want to clip the string at [max_size]; however, the character at that position
|
||||
// may span several bytes. We need to find the first byte of the character. In UTF-8
|
||||
// encoded data any byte that matches the bit pattern 10XXXXXX is not a start byte.
|
||||
// Therefore we decrement the index as long as there are bytes matching this pattern.
|
||||
// This ensures we cut the string at the border between one character and another.
|
||||
while index > 0 && unsafe { (*p.offset(index as isize) & 0b11000000) == 0b10000000 } {
|
||||
while index > 0 && (p[index] & 0b11000000) == 0b10000000 {
|
||||
index -= 1;
|
||||
}
|
||||
&string[0..index]
|
||||
|
||||
@@ -2,7 +2,6 @@ mod extractor;
|
||||
|
||||
extern crate num_cpus;
|
||||
|
||||
use clap;
|
||||
use flate2::write::GzEncoder;
|
||||
use rayon::prelude::*;
|
||||
use std::fs;
|
||||
@@ -57,7 +56,7 @@ impl TrapCompression {
|
||||
* (minimum of 1). If unspecified, should be considered as set to -1."
|
||||
*/
|
||||
fn num_codeql_threads() -> usize {
|
||||
let threads_str = std::env::var("CODEQL_THREADS").unwrap_or("-1".to_owned());
|
||||
let threads_str = std::env::var("CODEQL_THREADS").unwrap_or_else(|_| "-1".to_owned());
|
||||
match threads_str.parse::<i32>() {
|
||||
Ok(num) if num <= 0 => {
|
||||
let reduction = -num as usize;
|
||||
@@ -124,6 +123,10 @@ fn main() -> std::io::Result<()> {
|
||||
|
||||
let language = tree_sitter_ruby::language();
|
||||
let erb = tree_sitter_embedded_template::language();
|
||||
// Look up tree-sitter kind ids now, to avoid string comparisons when scanning ERB files.
|
||||
let erb_directive_id = erb.id_for_node_kind("directive", true);
|
||||
let erb_output_directive_id = erb.id_for_node_kind("output_directive", true);
|
||||
let erb_code_id = erb.id_for_node_kind("code", true);
|
||||
let schema = node_types::read_node_types_str("ruby", tree_sitter_ruby::NODE_TYPES)?;
|
||||
let erb_schema =
|
||||
node_types::read_node_types_str("erb", tree_sitter_embedded_template::NODE_TYPES)?;
|
||||
@@ -149,7 +152,13 @@ fn main() -> std::io::Result<()> {
|
||||
&[],
|
||||
)?;
|
||||
|
||||
let (ranges, line_breaks) = scan_erb(erb, &source);
|
||||
let (ranges, line_breaks) = scan_erb(
|
||||
erb,
|
||||
&source,
|
||||
erb_directive_id,
|
||||
erb_output_directive_id,
|
||||
erb_code_id,
|
||||
);
|
||||
for i in line_breaks {
|
||||
if i < source.len() {
|
||||
source[i] = b'\n';
|
||||
@@ -181,12 +190,12 @@ fn main() -> std::io::Result<()> {
|
||||
}
|
||||
|
||||
fn write_trap(
|
||||
trap_dir: &PathBuf,
|
||||
trap_dir: &Path,
|
||||
path: PathBuf,
|
||||
trap_writer: extractor::TrapWriter,
|
||||
trap_compression: &TrapCompression,
|
||||
) -> std::io::Result<()> {
|
||||
let trap_file = path_for(&trap_dir, &path, trap_compression.extension());
|
||||
let trap_file = path_for(trap_dir, &path, trap_compression.extension());
|
||||
std::fs::create_dir_all(&trap_file.parent().unwrap())?;
|
||||
let trap_file = std::fs::File::create(&trap_file)?;
|
||||
let mut trap_file = BufWriter::new(trap_file);
|
||||
@@ -199,7 +208,13 @@ fn write_trap(
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_erb(erb: Language, source: &std::vec::Vec<u8>) -> (Vec<Range>, Vec<usize>) {
|
||||
fn scan_erb(
|
||||
erb: Language,
|
||||
source: &[u8],
|
||||
directive_id: u16,
|
||||
output_directive_id: u16,
|
||||
code_id: u16,
|
||||
) -> (Vec<Range>, Vec<usize>) {
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(erb).unwrap();
|
||||
let tree = parser.parse(&source, None).expect("Failed to parse file");
|
||||
@@ -207,10 +222,10 @@ fn scan_erb(erb: Language, source: &std::vec::Vec<u8>) -> (Vec<Range>, Vec<usize
|
||||
let mut line_breaks = vec![];
|
||||
|
||||
for n in tree.root_node().children(&mut tree.walk()) {
|
||||
let kind = n.kind();
|
||||
if kind == "directive" || kind == "output_directive" {
|
||||
let kind_id = n.kind_id();
|
||||
if kind_id == directive_id || kind_id == output_directive_id {
|
||||
for c in n.children(&mut tree.walk()) {
|
||||
if c.kind() == "code" {
|
||||
if c.kind_id() == code_id {
|
||||
let mut range = c.range();
|
||||
if range.end_byte < source.len() {
|
||||
line_breaks.push(range.end_byte);
|
||||
@@ -222,7 +237,7 @@ fn scan_erb(erb: Language, source: &std::vec::Vec<u8>) -> (Vec<Range>, Vec<usize
|
||||
}
|
||||
}
|
||||
}
|
||||
if result.len() == 0 {
|
||||
if result.is_empty() {
|
||||
let root = tree.root_node();
|
||||
// Add an empty range at the end of the file
|
||||
result.push(Range {
|
||||
|
||||
@@ -68,10 +68,10 @@ impl<'a> fmt::Display for Table<'a> {
|
||||
}
|
||||
write!(f, "{}", key)?;
|
||||
}
|
||||
write!(f, "]\n")?;
|
||||
writeln!(f, "]")?;
|
||||
}
|
||||
|
||||
write!(f, "{}(\n", self.name)?;
|
||||
writeln!(f, "{}(", self.name)?;
|
||||
for (column_index, column) in self.columns.iter().enumerate() {
|
||||
write!(f, " ")?;
|
||||
if column.unique {
|
||||
@@ -92,7 +92,7 @@ impl<'a> fmt::Display for Table<'a> {
|
||||
if column_index + 1 != self.columns.len() {
|
||||
write!(f, ",")?;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
write!(f, ");")?;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ mod language;
|
||||
mod ql;
|
||||
mod ql_gen;
|
||||
|
||||
use clap;
|
||||
use language::Language;
|
||||
use std::collections::BTreeMap as Map;
|
||||
use std::collections::BTreeSet as Set;
|
||||
@@ -33,7 +32,7 @@ fn make_field_type<'a>(
|
||||
.map(|t| nodes.get(t).unwrap().dbscheme_name.as_str())
|
||||
.collect();
|
||||
(
|
||||
ql::Type::AtType(&dbscheme_union),
|
||||
ql::Type::At(dbscheme_union),
|
||||
Some(dbscheme::Entry::Union(dbscheme::Union {
|
||||
name: dbscheme_union,
|
||||
members,
|
||||
@@ -41,14 +40,14 @@ fn make_field_type<'a>(
|
||||
)
|
||||
}
|
||||
node_types::FieldTypeInfo::Single(t) => {
|
||||
let dbscheme_name = &nodes.get(&t).unwrap().dbscheme_name;
|
||||
(ql::Type::AtType(dbscheme_name), None)
|
||||
let dbscheme_name = &nodes.get(t).unwrap().dbscheme_name;
|
||||
(ql::Type::At(dbscheme_name), None)
|
||||
}
|
||||
node_types::FieldTypeInfo::ReservedWordInt(int_mapping) => {
|
||||
// The field will be an `int` in the db, and we add a case split to
|
||||
// create other db types for each integer value.
|
||||
let mut branches: Vec<(usize, &'a str)> = Vec::new();
|
||||
for (_, (value, name)) in int_mapping {
|
||||
for (value, name) in int_mapping.values() {
|
||||
branches.push((*value, name));
|
||||
}
|
||||
let case = dbscheme::Entry::Case(dbscheme::Case {
|
||||
@@ -74,12 +73,12 @@ fn add_field_for_table_storage<'a>(
|
||||
let parent_name = &nodes.get(&field.parent).unwrap().dbscheme_name;
|
||||
// This field can appear zero or multiple times, so put
|
||||
// it in an auxiliary table.
|
||||
let (field_ql_type, field_type_entry) = make_field_type(parent_name, &field, nodes);
|
||||
let (field_ql_type, field_type_entry) = make_field_type(parent_name, field, nodes);
|
||||
let parent_column = dbscheme::Column {
|
||||
unique: !has_index,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: &parent_name,
|
||||
ql_type: ql::Type::AtType(&parent_name),
|
||||
name: parent_name,
|
||||
ql_type: ql::Type::At(parent_name),
|
||||
ql_type_is_ref: true,
|
||||
};
|
||||
let index_column = dbscheme::Column {
|
||||
@@ -97,7 +96,7 @@ fn add_field_for_table_storage<'a>(
|
||||
ql_type_is_ref: true,
|
||||
};
|
||||
let field_table = dbscheme::Table {
|
||||
name: &table_name,
|
||||
name: table_name,
|
||||
columns: if has_index {
|
||||
vec![parent_column, index_column, field_column]
|
||||
} else {
|
||||
@@ -106,7 +105,7 @@ fn add_field_for_table_storage<'a>(
|
||||
// In addition to the field being unique, the combination of
|
||||
// parent+index is unique, so add a keyset for them.
|
||||
keysets: if has_index {
|
||||
Some(vec![&parent_name, "index"])
|
||||
Some(vec![parent_name, "index"])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -122,7 +121,7 @@ fn add_field_for_column_storage<'a>(
|
||||
) -> (dbscheme::Column<'a>, Option<dbscheme::Entry<'a>>) {
|
||||
// This field must appear exactly once, so we add it as
|
||||
// a column to the main table for the node type.
|
||||
let (field_ql_type, field_type_entry) = make_field_type(parent_name, &field, nodes);
|
||||
let (field_ql_type, field_type_entry) = make_field_type(parent_name, field, nodes);
|
||||
(
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
@@ -142,9 +141,9 @@ fn add_field_for_column_storage<'a>(
|
||||
/// 2. A set of names of the members of the `<lang>_ast_node` union.
|
||||
/// 3. A map where the keys are the dbscheme names for token kinds, and the
|
||||
/// values are their integer representations.
|
||||
fn convert_nodes<'a>(
|
||||
nodes: &'a node_types::NodeTypeMap,
|
||||
) -> (Vec<dbscheme::Entry<'a>>, Set<&'a str>, Map<&'a str, usize>) {
|
||||
fn convert_nodes(
|
||||
nodes: &node_types::NodeTypeMap,
|
||||
) -> (Vec<dbscheme::Entry>, Set<&str>, Map<&str, usize>) {
|
||||
let mut entries: Vec<dbscheme::Entry> = Vec::new();
|
||||
let mut ast_node_members: Set<&str> = Set::new();
|
||||
let token_kinds: Map<&str, usize> = nodes
|
||||
@@ -156,7 +155,7 @@ fn convert_nodes<'a>(
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
for (_, node) in nodes {
|
||||
for node in nodes.values() {
|
||||
match &node.kind {
|
||||
node_types::EntryKind::Union { members: n_members } => {
|
||||
// It's a tree-sitter supertype node, for which we create a union
|
||||
@@ -173,12 +172,12 @@ fn convert_nodes<'a>(
|
||||
node_types::EntryKind::Table { name, fields } => {
|
||||
// It's a product type, defined by a table.
|
||||
let mut main_table = dbscheme::Table {
|
||||
name: &name,
|
||||
name,
|
||||
columns: vec![dbscheme::Column {
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
unique: true,
|
||||
ql_type: ql::Type::AtType(&node.dbscheme_name),
|
||||
ql_type: ql::Type::At(&node.dbscheme_name),
|
||||
ql_type_is_ref: false,
|
||||
}],
|
||||
keysets: None,
|
||||
@@ -238,7 +237,7 @@ fn convert_nodes<'a>(
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "loc",
|
||||
ql_type: ql::Type::AtType("location"),
|
||||
ql_type: ql::Type::At("location"),
|
||||
ql_type_is_ref: true,
|
||||
});
|
||||
|
||||
@@ -264,14 +263,14 @@ fn create_ast_node_parent_table<'a>(name: &'a str, ast_node_name: &'a str) -> db
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "child",
|
||||
unique: false,
|
||||
ql_type: ql::Type::AtType(ast_node_name),
|
||||
ql_type: ql::Type::At(ast_node_name),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "parent",
|
||||
unique: false,
|
||||
ql_type: ql::Type::AtType(name),
|
||||
ql_type: ql::Type::At(name),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
@@ -295,7 +294,7 @@ fn create_tokeninfo<'a>(name: &'a str, type_name: &'a str) -> dbscheme::Table<'a
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
unique: true,
|
||||
ql_type: ql::Type::AtType(type_name),
|
||||
ql_type: ql::Type::At(type_name),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
@@ -305,20 +304,6 @@ fn create_tokeninfo<'a>(name: &'a str, type_name: &'a str) -> dbscheme::Table<'a
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "file",
|
||||
ql_type: ql::Type::AtType("file"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "idx",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
@@ -330,7 +315,7 @@ fn create_tokeninfo<'a>(name: &'a str, type_name: &'a str) -> dbscheme::Table<'a
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "loc",
|
||||
ql_type: ql::Type::AtType("location"),
|
||||
ql_type: ql::Type::At("location"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
@@ -343,9 +328,9 @@ fn create_token_case<'a>(name: &'a str, token_kinds: Map<&'a str, usize>) -> dbs
|
||||
.map(|(&name, kind_id)| (*kind_id, name))
|
||||
.collect();
|
||||
dbscheme::Case {
|
||||
name: name,
|
||||
name,
|
||||
column: "kind",
|
||||
branches: branches,
|
||||
branches,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +350,7 @@ fn create_files_table<'a>() -> dbscheme::Entry<'a> {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
ql_type: ql::Type::AtType("file"),
|
||||
ql_type: ql::Type::At("file"),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
@@ -387,7 +372,7 @@ fn create_folders_table<'a>() -> dbscheme::Entry<'a> {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
ql_type: ql::Type::AtType("folder"),
|
||||
ql_type: ql::Type::At("folder"),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
@@ -410,14 +395,14 @@ fn create_locations_default_table<'a>() -> dbscheme::Entry<'a> {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
ql_type: ql::Type::AtType("location_default"),
|
||||
ql_type: ql::Type::At("location_default"),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "file",
|
||||
ql_type: ql::Type::AtType("file"),
|
||||
ql_type: ql::Type::At("file"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
@@ -467,14 +452,14 @@ fn create_containerparent_table<'a>() -> dbscheme::Entry<'a> {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "parent",
|
||||
ql_type: ql::Type::AtType("container"),
|
||||
ql_type: ql::Type::At("container"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "child",
|
||||
ql_type: ql::Type::AtType("container"),
|
||||
ql_type: ql::Type::At("container"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
@@ -505,7 +490,7 @@ fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
ql_type: ql::Type::AtType("diagnostic"),
|
||||
ql_type: ql::Type::At("diagnostic"),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
@@ -540,7 +525,7 @@ fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "location",
|
||||
ql_type: ql::Type::AtType("location_default"),
|
||||
ql_type: ql::Type::At("location_default"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
@@ -639,7 +624,7 @@ fn main() -> std::io::Result<()> {
|
||||
let token_name = format!("{}_token", &prefix);
|
||||
let tokeninfo_name = format!("{}_tokeninfo", &prefix);
|
||||
let reserved_word_name = format!("{}_reserved_word", &prefix);
|
||||
let nodes = node_types::read_node_types_str(&prefix, &language.node_types)?;
|
||||
let nodes = node_types::read_node_types_str(&prefix, language.node_types)?;
|
||||
let (dbscheme_entries, mut ast_node_members, token_kinds) = convert_nodes(&nodes);
|
||||
ast_node_members.insert(&token_name);
|
||||
dbscheme::write(&mut dbscheme_writer, &dbscheme_entries)?;
|
||||
|
||||
@@ -43,15 +43,15 @@ impl<'a> fmt::Display for Class<'a> {
|
||||
}
|
||||
write!(f, "{}", supertype)?;
|
||||
}
|
||||
write!(f, " {{ \n")?;
|
||||
writeln!(f, " {{ ")?;
|
||||
|
||||
if let Some(charpred) = &self.characteristic_predicate {
|
||||
write!(
|
||||
writeln!(
|
||||
f,
|
||||
" {}\n",
|
||||
" {}",
|
||||
Predicate {
|
||||
qldoc: None,
|
||||
name: self.name.clone(),
|
||||
name: self.name,
|
||||
overridden: false,
|
||||
return_type: None,
|
||||
formal_parameters: vec![],
|
||||
@@ -61,7 +61,7 @@ impl<'a> fmt::Display for Class<'a> {
|
||||
}
|
||||
|
||||
for predicate in &self.predicates {
|
||||
write!(f, " {}\n", predicate)?;
|
||||
writeln!(f, " {}", predicate)?;
|
||||
}
|
||||
|
||||
write!(f, "}}")?;
|
||||
@@ -82,9 +82,9 @@ impl<'a> fmt::Display for Module<'a> {
|
||||
if let Some(qldoc) = &self.qldoc {
|
||||
write!(f, "/** {} */", qldoc)?;
|
||||
}
|
||||
write!(f, "module {} {{ \n", self.name)?;
|
||||
writeln!(f, "module {} {{ ", self.name)?;
|
||||
for decl in &self.body {
|
||||
write!(f, " {}\n", decl)?;
|
||||
writeln!(f, " {}", decl)?;
|
||||
}
|
||||
write!(f, "}}")?;
|
||||
Ok(())
|
||||
@@ -100,7 +100,7 @@ pub enum Type<'a> {
|
||||
String,
|
||||
|
||||
/// A database type that will need to be referred to with an `@` prefix.
|
||||
AtType(&'a str),
|
||||
At(&'a str),
|
||||
|
||||
/// A user-defined type.
|
||||
Normal(&'a str),
|
||||
@@ -112,7 +112,7 @@ impl<'a> fmt::Display for Type<'a> {
|
||||
Type::Int => write!(f, "int"),
|
||||
Type::String => write!(f, "string"),
|
||||
Type::Normal(name) => write!(f, "{}", name),
|
||||
Type::AtType(name) => write!(f, "@{}", name),
|
||||
Type::At(name) => write!(f, "@{}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,7 @@ impl<'a> fmt::Display for Expression<'a> {
|
||||
second_expr,
|
||||
} => {
|
||||
write!(f, "{}(", name)?;
|
||||
if vars.len() > 0 {
|
||||
if !vars.is_empty() {
|
||||
for (index, var) in vars.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
|
||||
@@ -104,7 +104,7 @@ pub fn create_ast_node_class<'a>(ast_node: &'a str, ast_node_parent: &'a str) ->
|
||||
qldoc: Some(String::from("The base class for all AST nodes")),
|
||||
name: "AstNode",
|
||||
is_abstract: false,
|
||||
supertypes: vec![ql::Type::AtType(ast_node)].into_iter().collect(),
|
||||
supertypes: vec![ql::Type::At(ast_node)].into_iter().collect(),
|
||||
characteristic_predicate: None,
|
||||
predicates: vec![
|
||||
to_string,
|
||||
@@ -119,14 +119,14 @@ pub fn create_ast_node_class<'a>(ast_node: &'a str, ast_node_parent: &'a str) ->
|
||||
}
|
||||
|
||||
pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Class<'a> {
|
||||
let tokeninfo_arity = 6;
|
||||
let tokeninfo_arity = 4;
|
||||
let get_value = ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the value of this token.")),
|
||||
name: "getValue",
|
||||
overridden: false,
|
||||
return_type: Some(ql::Type::String),
|
||||
formal_parameters: vec![],
|
||||
body: create_get_field_expr_for_column_storage("result", tokeninfo, 3, tokeninfo_arity),
|
||||
body: create_get_field_expr_for_column_storage("result", tokeninfo, 1, tokeninfo_arity),
|
||||
};
|
||||
let get_location = ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the location of this token.")),
|
||||
@@ -134,7 +134,7 @@ pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Cl
|
||||
overridden: true,
|
||||
return_type: Some(ql::Type::Normal("Location")),
|
||||
formal_parameters: vec![],
|
||||
body: create_get_field_expr_for_column_storage("result", tokeninfo, 4, tokeninfo_arity),
|
||||
body: create_get_field_expr_for_column_storage("result", tokeninfo, 2, tokeninfo_arity),
|
||||
};
|
||||
let to_string = ql::Predicate {
|
||||
qldoc: Some(String::from(
|
||||
@@ -153,7 +153,7 @@ pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Cl
|
||||
qldoc: Some(String::from("A token.")),
|
||||
name: "Token",
|
||||
is_abstract: false,
|
||||
supertypes: vec![ql::Type::AtType(token_type), ql::Type::Normal("AstNode")]
|
||||
supertypes: vec![ql::Type::At(token_type), ql::Type::Normal("AstNode")]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
characteristic_predicate: None,
|
||||
@@ -167,14 +167,14 @@ pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Cl
|
||||
}
|
||||
|
||||
// Creates the `ReservedWord` class.
|
||||
pub fn create_reserved_word_class<'a>(db_name: &'a str) -> ql::Class<'a> {
|
||||
pub fn create_reserved_word_class(db_name: &str) -> ql::Class {
|
||||
let class_name = "ReservedWord";
|
||||
let get_a_primary_ql_class = create_get_a_primary_ql_class(&class_name);
|
||||
let get_a_primary_ql_class = create_get_a_primary_ql_class(class_name);
|
||||
ql::Class {
|
||||
qldoc: Some(String::from("A reserved word.")),
|
||||
name: class_name,
|
||||
is_abstract: false,
|
||||
supertypes: vec![ql::Type::AtType(db_name), ql::Type::Normal("Token")]
|
||||
supertypes: vec![ql::Type::At(db_name), ql::Type::Normal("Token")]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
characteristic_predicate: None,
|
||||
@@ -190,8 +190,8 @@ fn create_none_predicate<'a>(
|
||||
return_type: Option<ql::Type<'a>>,
|
||||
) -> ql::Predicate<'a> {
|
||||
ql::Predicate {
|
||||
qldoc: qldoc,
|
||||
name: name,
|
||||
qldoc,
|
||||
name,
|
||||
overridden,
|
||||
return_type,
|
||||
formal_parameters: Vec::new(),
|
||||
@@ -201,7 +201,7 @@ fn create_none_predicate<'a>(
|
||||
|
||||
/// Creates an overridden `getAPrimaryQlClass` predicate that returns the given
|
||||
/// name.
|
||||
fn create_get_a_primary_ql_class<'a>(class_name: &'a str) -> ql::Predicate<'a> {
|
||||
fn create_get_a_primary_ql_class(class_name: &str) -> ql::Predicate {
|
||||
ql::Predicate {
|
||||
qldoc: Some(String::from(
|
||||
"Gets the name of the primary QL class for this element.",
|
||||
@@ -223,7 +223,7 @@ fn create_get_a_primary_ql_class<'a>(class_name: &'a str) -> ql::Predicate<'a> {
|
||||
///
|
||||
/// `def_table` - the name of the table that defines the entity and its location.
|
||||
/// `arity` - the total number of columns in the table
|
||||
fn create_get_location_predicate<'a>(def_table: &'a str, arity: usize) -> ql::Predicate<'a> {
|
||||
fn create_get_location_predicate(def_table: &str, arity: usize) -> ql::Predicate {
|
||||
ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the location of this element.")),
|
||||
name: "getLocation",
|
||||
@@ -248,7 +248,7 @@ fn create_get_location_predicate<'a>(def_table: &'a str, arity: usize) -> ql::Pr
|
||||
/// # Arguments
|
||||
///
|
||||
/// `def_table` - the name of the table that defines the entity and its text.
|
||||
fn create_get_text_predicate<'a>(def_table: &'a str) -> ql::Predicate<'a> {
|
||||
fn create_get_text_predicate(def_table: &str) -> ql::Predicate {
|
||||
ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the text content of this element.")),
|
||||
name: "getText",
|
||||
@@ -339,13 +339,13 @@ fn create_field_getters<'a>(
|
||||
) -> (ql::Predicate<'a>, Option<ql::Expression<'a>>) {
|
||||
let return_type = match &field.type_info {
|
||||
node_types::FieldTypeInfo::Single(t) => {
|
||||
Some(ql::Type::Normal(&nodes.get(&t).unwrap().ql_class_name))
|
||||
Some(ql::Type::Normal(&nodes.get(t).unwrap().ql_class_name))
|
||||
}
|
||||
node_types::FieldTypeInfo::Multiple {
|
||||
types: _,
|
||||
dbscheme_union: _,
|
||||
ql_class,
|
||||
} => Some(ql::Type::Normal(&ql_class)),
|
||||
} => Some(ql::Type::Normal(ql_class)),
|
||||
node_types::FieldTypeInfo::ReservedWordInt(_) => Some(ql::Type::String),
|
||||
};
|
||||
let formal_parameters = match &field.storage {
|
||||
@@ -381,13 +381,13 @@ fn create_field_getters<'a>(
|
||||
(
|
||||
create_get_field_expr_for_column_storage(
|
||||
get_value_result_var_name,
|
||||
&main_table_name,
|
||||
main_table_name,
|
||||
column_index,
|
||||
main_table_arity,
|
||||
),
|
||||
create_get_field_expr_for_column_storage(
|
||||
get_value_result_var_name,
|
||||
&main_table_name,
|
||||
main_table_name,
|
||||
column_index,
|
||||
main_table_arity,
|
||||
),
|
||||
@@ -400,12 +400,12 @@ fn create_field_getters<'a>(
|
||||
} => (
|
||||
create_get_field_expr_for_table_storage(
|
||||
get_value_result_var_name,
|
||||
&field_table_name,
|
||||
field_table_name,
|
||||
if *has_index { Some("i") } else { None },
|
||||
),
|
||||
create_get_field_expr_for_table_storage(
|
||||
get_value_result_var_name,
|
||||
&field_table_name,
|
||||
field_table_name,
|
||||
if *has_index { Some("_") } else { None },
|
||||
),
|
||||
),
|
||||
@@ -453,7 +453,7 @@ fn create_field_getters<'a>(
|
||||
let qldoc = match &field.name {
|
||||
Some(name) => format!("Gets the node corresponding to the field `{}`.", name),
|
||||
None => {
|
||||
if formal_parameters.len() == 0 {
|
||||
if formal_parameters.is_empty() {
|
||||
"Gets the child of this node.".to_owned()
|
||||
} else {
|
||||
"Gets the `i`th child of this node.".to_owned()
|
||||
@@ -474,7 +474,7 @@ fn create_field_getters<'a>(
|
||||
}
|
||||
|
||||
/// Converts the given node types into CodeQL classes wrapping the dbscheme.
|
||||
pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel<'a>> {
|
||||
pub fn convert_nodes(nodes: &node_types::NodeTypeMap) -> Vec<ql::TopLevel> {
|
||||
let mut classes: Vec<ql::TopLevel> = Vec::new();
|
||||
let mut token_kinds = BTreeSet::new();
|
||||
for (type_name, node) in nodes {
|
||||
@@ -491,7 +491,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel
|
||||
if type_name.named {
|
||||
let get_a_primary_ql_class = create_get_a_primary_ql_class(&node.ql_class_name);
|
||||
let mut supertypes: BTreeSet<ql::Type> = BTreeSet::new();
|
||||
supertypes.insert(ql::Type::AtType(&node.dbscheme_name));
|
||||
supertypes.insert(ql::Type::At(&node.dbscheme_name));
|
||||
supertypes.insert(ql::Type::Normal("Token"));
|
||||
classes.push(ql::TopLevel::Class(ql::Class {
|
||||
qldoc: Some(format!("A class representing `{}` tokens.", type_name.kind)),
|
||||
@@ -511,7 +511,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel
|
||||
name: &node.ql_class_name,
|
||||
is_abstract: false,
|
||||
supertypes: vec![
|
||||
ql::Type::AtType(&node.dbscheme_name),
|
||||
ql::Type::At(&node.dbscheme_name),
|
||||
ql::Type::Normal("AstNode"),
|
||||
]
|
||||
.into_iter()
|
||||
@@ -542,25 +542,25 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel
|
||||
let main_class_name = &node.ql_class_name;
|
||||
let mut main_class = ql::Class {
|
||||
qldoc: Some(format!("A class representing `{}` nodes.", type_name.kind)),
|
||||
name: &main_class_name,
|
||||
name: main_class_name,
|
||||
is_abstract: false,
|
||||
supertypes: vec![
|
||||
ql::Type::AtType(&node.dbscheme_name),
|
||||
ql::Type::At(&node.dbscheme_name),
|
||||
ql::Type::Normal("AstNode"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
characteristic_predicate: None,
|
||||
predicates: vec![
|
||||
create_get_a_primary_ql_class(&main_class_name),
|
||||
create_get_location_predicate(&main_table_name, main_table_arity),
|
||||
create_get_a_primary_ql_class(main_class_name),
|
||||
create_get_location_predicate(main_table_name, main_table_arity),
|
||||
],
|
||||
};
|
||||
|
||||
if fields.is_empty() {
|
||||
main_class
|
||||
.predicates
|
||||
.push(create_get_text_predicate(&main_table_name));
|
||||
.push(create_get_text_predicate(main_table_name));
|
||||
} else {
|
||||
let mut main_table_column_index: usize = 0;
|
||||
let mut get_child_exprs: Vec<ql::Expression> = Vec::new();
|
||||
@@ -571,7 +571,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel
|
||||
// - the QL expressions to access the fields that will be part of getAFieldOrChild.
|
||||
for field in fields {
|
||||
let (get_pred, get_child_expr) = create_field_getters(
|
||||
&main_table_name,
|
||||
main_table_name,
|
||||
main_table_arity,
|
||||
&mut main_table_column_index,
|
||||
field,
|
||||
|
||||
@@ -83,13 +83,13 @@ pub enum Storage {
|
||||
|
||||
pub fn read_node_types(prefix: &str, node_types_path: &Path) -> std::io::Result<NodeTypeMap> {
|
||||
let file = fs::File::open(node_types_path)?;
|
||||
let node_types = serde_json::from_reader(file)?;
|
||||
Ok(convert_nodes(&prefix, &node_types))
|
||||
let node_types: Vec<NodeInfo> = serde_json::from_reader(file)?;
|
||||
Ok(convert_nodes(prefix, &node_types))
|
||||
}
|
||||
|
||||
pub fn read_node_types_str(prefix: &str, node_types_json: &str) -> std::io::Result<NodeTypeMap> {
|
||||
let node_types = serde_json::from_str(node_types_json)?;
|
||||
Ok(convert_nodes(&prefix, &node_types))
|
||||
let node_types: Vec<NodeInfo> = serde_json::from_str(node_types_json)?;
|
||||
Ok(convert_nodes(prefix, &node_types))
|
||||
}
|
||||
|
||||
fn convert_type(node_type: &NodeType) -> TypeName {
|
||||
@@ -99,31 +99,31 @@ fn convert_type(node_type: &NodeType) -> TypeName {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_types(node_types: &Vec<NodeType>) -> Set<TypeName> {
|
||||
let iter = node_types.iter().map(convert_type).collect();
|
||||
std::collections::BTreeSet::from(iter)
|
||||
fn convert_types(node_types: &[NodeType]) -> Set<TypeName> {
|
||||
node_types.iter().map(convert_type).collect()
|
||||
}
|
||||
|
||||
pub fn convert_nodes(prefix: &str, nodes: &Vec<NodeInfo>) -> NodeTypeMap {
|
||||
pub fn convert_nodes(prefix: &str, nodes: &[NodeInfo]) -> NodeTypeMap {
|
||||
let mut entries = NodeTypeMap::new();
|
||||
let mut token_kinds = Set::new();
|
||||
|
||||
// First, find all the token kinds
|
||||
for node in nodes {
|
||||
if node.subtypes.is_none() {
|
||||
if node.fields.as_ref().map_or(0, |x| x.len()) == 0 && node.children.is_none() {
|
||||
let type_name = TypeName {
|
||||
kind: node.kind.clone(),
|
||||
named: node.named,
|
||||
};
|
||||
token_kinds.insert(type_name);
|
||||
}
|
||||
if node.subtypes.is_none()
|
||||
&& node.fields.as_ref().map_or(0, |x| x.len()) == 0
|
||||
&& node.children.is_none()
|
||||
{
|
||||
let type_name = TypeName {
|
||||
kind: node.kind.clone(),
|
||||
named: node.named,
|
||||
};
|
||||
token_kinds.insert(type_name);
|
||||
}
|
||||
}
|
||||
|
||||
for node in nodes {
|
||||
let flattened_name = &node_type_name(&node.kind, node.named);
|
||||
let dbscheme_name = escape_name(&flattened_name);
|
||||
let dbscheme_name = escape_name(flattened_name);
|
||||
let ql_class_name = dbscheme_name_to_class_name(&dbscheme_name);
|
||||
let dbscheme_name = format!("{}_{}", prefix, &dbscheme_name);
|
||||
if let Some(subtypes) = &node.subtypes {
|
||||
@@ -138,7 +138,7 @@ pub fn convert_nodes(prefix: &str, nodes: &Vec<NodeInfo>) -> NodeTypeMap {
|
||||
dbscheme_name,
|
||||
ql_class_name,
|
||||
kind: EntryKind::Union {
|
||||
members: convert_types(&subtypes),
|
||||
members: convert_types(subtypes),
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -160,7 +160,7 @@ pub fn convert_nodes(prefix: &str, nodes: &Vec<NodeInfo>) -> NodeTypeMap {
|
||||
if let Some(node_fields) = &node.fields {
|
||||
for (field_name, field_info) in node_fields {
|
||||
add_field(
|
||||
&prefix,
|
||||
prefix,
|
||||
&type_name,
|
||||
Some(field_name.to_string()),
|
||||
field_info,
|
||||
@@ -172,7 +172,7 @@ pub fn convert_nodes(prefix: &str, nodes: &Vec<NodeInfo>) -> NodeTypeMap {
|
||||
if let Some(children) = &node.children {
|
||||
// Treat children as if they were a field called 'child'.
|
||||
add_field(
|
||||
&prefix,
|
||||
prefix,
|
||||
&type_name,
|
||||
None,
|
||||
children,
|
||||
@@ -253,13 +253,11 @@ fn add_field(
|
||||
// All possible types for this field are reserved words. The db
|
||||
// representation will be an `int` with a `case @foo.field = ...` to
|
||||
// enumerate the possible values.
|
||||
let mut counter = 0;
|
||||
let mut field_token_ints: BTreeMap<String, (usize, String)> = BTreeMap::new();
|
||||
for t in converted_types {
|
||||
for (counter, t) in converted_types.into_iter().enumerate() {
|
||||
let dbscheme_variant_name =
|
||||
escape_name(&format!("{}_{}_{}", &prefix, parent_flattened_name, t.kind));
|
||||
field_token_ints.insert(t.kind.to_owned(), (counter, dbscheme_variant_name));
|
||||
counter += 1;
|
||||
}
|
||||
FieldTypeInfo::ReservedWordInt(field_token_ints)
|
||||
} else if field_info.types.len() == 1 {
|
||||
@@ -330,7 +328,7 @@ fn node_type_name(kind: &str, named: bool) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
const RESERVED_KEYWORDS: [&'static str; 14] = [
|
||||
const RESERVED_KEYWORDS: [&str; 14] = [
|
||||
"boolean", "case", "date", "float", "int", "key", "of", "order", "ref", "string", "subtype",
|
||||
"type", "unique", "varchar",
|
||||
];
|
||||
|
||||
@@ -6,7 +6,7 @@ import files.FileSystem
|
||||
* A location as given by a file, a start line, a start column,
|
||||
* an end line, and an end column.
|
||||
*
|
||||
* For more information about locations see [LGTM locations](https://lgtm.com/help/ql/locations).
|
||||
* For more information about locations see [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
class Location extends @location {
|
||||
/** Gets the file for this location. */
|
||||
@@ -40,7 +40,7 @@ class Location extends @location {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [LGTM locations](https://lgtm.com/help/ql/locations).
|
||||
* [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -141,7 +141,7 @@ abstract class Container extends @container {
|
||||
/**
|
||||
* Gets a URL representing the location of this container.
|
||||
*
|
||||
* For more information see https://lgtm.com/help/ql/locations#providing-urls.
|
||||
* For more information see https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls.
|
||||
*/
|
||||
abstract string getURL();
|
||||
|
||||
|
||||
@@ -503,3 +503,38 @@ module CodeExecution {
|
||||
abstract DataFlow::Node getCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that parses XML content.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `XmlParserCall::Range` instead.
|
||||
*/
|
||||
class XmlParserCall extends DataFlow::Node {
|
||||
XmlParserCall::Range range;
|
||||
|
||||
XmlParserCall() { this = range }
|
||||
|
||||
/** Gets the argument that specifies the XML content to be parsed. */
|
||||
DataFlow::Node getInput() { result = range.getInput() }
|
||||
|
||||
/** Holds if this XML parser call is configured to process external entities */
|
||||
predicate externalEntitiesEnabled() { range.externalEntitiesEnabled() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new XML parsing APIs. */
|
||||
module XmlParserCall {
|
||||
/**
|
||||
* A data-flow node that parses XML content.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `class XmlParserCall` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the XML content to be parsed. */
|
||||
abstract DataFlow::Node getInput();
|
||||
|
||||
/** Holds if this XML parser call is configured to process external entities */
|
||||
abstract predicate externalEntitiesEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.StandardLibrary
|
||||
private import codeql.ruby.frameworks.Files
|
||||
private import codeql.ruby.frameworks.HTTPClients
|
||||
private import codeql.ruby.frameworks.HttpClients
|
||||
private import codeql.ruby.frameworks.XmlParsing
|
||||
|
||||
@@ -8,7 +8,7 @@ private import codeql.ruby.dataflow.internal.DataFlowImplCommon
|
||||
/**
|
||||
* A call.
|
||||
*/
|
||||
class Call extends Expr, TCall {
|
||||
class Call extends Expr instanceof CallImpl {
|
||||
override string getAPrimaryQlClass() { result = "Call" }
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ class Call extends Expr, TCall {
|
||||
* yield 0, bar: 1
|
||||
* ```
|
||||
*/
|
||||
final Expr getArgument(int n) { result = this.(CallImpl).getArgumentImpl(n) }
|
||||
final Expr getArgument(int n) { result = super.getArgumentImpl(n) }
|
||||
|
||||
/**
|
||||
* Gets an argument of this method call.
|
||||
@@ -49,7 +49,7 @@ class Call extends Expr, TCall {
|
||||
/**
|
||||
* Gets the number of arguments of this method call.
|
||||
*/
|
||||
final int getNumberOfArguments() { result = this.(CallImpl).getNumberOfArgumentsImpl() }
|
||||
final int getNumberOfArguments() { result = super.getNumberOfArgumentsImpl() }
|
||||
|
||||
/** Gets a potential target of this call, if any. */
|
||||
final Callable getATarget() {
|
||||
@@ -59,7 +59,7 @@ class Call extends Expr, TCall {
|
||||
}
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
result = Expr.super.getAChild(pred)
|
||||
or
|
||||
pred = "getArgument" and result = this.getArgument(_)
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class Call extends Expr, TCall {
|
||||
/**
|
||||
* A method call.
|
||||
*/
|
||||
class MethodCall extends Call, TMethodCall {
|
||||
class MethodCall extends Call instanceof MethodCallImpl {
|
||||
override string getAPrimaryQlClass() { result = "MethodCall" }
|
||||
|
||||
/**
|
||||
@@ -84,7 +84,7 @@ class MethodCall extends Call, TMethodCall {
|
||||
* the call to `qux` is the `Expr` for `Baz`; for the call to `corge` there
|
||||
* is no result.
|
||||
*/
|
||||
final Expr getReceiver() { result = this.(MethodCallImpl).getReceiverImpl() }
|
||||
final Expr getReceiver() { result = super.getReceiverImpl() }
|
||||
|
||||
/**
|
||||
* Gets the name of the method being called. For example, in:
|
||||
@@ -95,7 +95,7 @@ class MethodCall extends Call, TMethodCall {
|
||||
*
|
||||
* the result is `"bar"`.
|
||||
*/
|
||||
final string getMethodName() { result = this.(MethodCallImpl).getMethodNameImpl() }
|
||||
final string getMethodName() { result = super.getMethodNameImpl() }
|
||||
|
||||
/**
|
||||
* Gets the block of this method call, if any.
|
||||
@@ -103,12 +103,12 @@ class MethodCall extends Call, TMethodCall {
|
||||
* foo.each { |x| puts x }
|
||||
* ```
|
||||
*/
|
||||
Block getBlock() { none() }
|
||||
final Block getBlock() { result = super.getBlockImpl() }
|
||||
|
||||
override string toString() { result = "call to " + this.getMethodName() }
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
result = Call.super.getAChild(pred)
|
||||
or
|
||||
pred = "getReceiver" and result = this.getReceiver()
|
||||
or
|
||||
@@ -123,7 +123,7 @@ class MethodCall extends Call, TMethodCall {
|
||||
* a[0] = 10
|
||||
* ```
|
||||
*/
|
||||
class SetterMethodCall extends MethodCall {
|
||||
class SetterMethodCall extends MethodCall, TMethodCallSynth {
|
||||
SetterMethodCall() { this = TMethodCallSynth(_, _, _, true, _) }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "SetterMethodCall" }
|
||||
@@ -135,7 +135,7 @@ class SetterMethodCall extends MethodCall {
|
||||
* a[0]
|
||||
* ```
|
||||
*/
|
||||
class ElementReference extends MethodCall, TElementReference {
|
||||
class ElementReference extends MethodCall instanceof ElementReferenceImpl {
|
||||
final override string getAPrimaryQlClass() { result = "ElementReference" }
|
||||
|
||||
final override string toString() { result = "...[...]" }
|
||||
@@ -147,11 +147,7 @@ class ElementReference extends MethodCall, TElementReference {
|
||||
* yield x, y
|
||||
* ```
|
||||
*/
|
||||
class YieldCall extends Call, TYieldCall {
|
||||
Ruby::Yield g;
|
||||
|
||||
YieldCall() { this = TYieldCall(g) }
|
||||
|
||||
class YieldCall extends Call instanceof YieldCallImpl {
|
||||
final override string getAPrimaryQlClass() { result = "YieldCall" }
|
||||
|
||||
final override string toString() { result = "yield ..." }
|
||||
@@ -167,7 +163,7 @@ class YieldCall extends Call, TYieldCall {
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class SuperCall extends MethodCall, TSuperCall {
|
||||
class SuperCall extends MethodCall instanceof SuperCallImpl {
|
||||
final override string getAPrimaryQlClass() { result = "SuperCall" }
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,8 @@ class ConstantReadAccess extends ConstantAccess {
|
||||
result = lookupConst(resolveScopeExpr(this.getScopeExpr()), this.getName())
|
||||
}
|
||||
|
||||
override string getValueText() { result = this.getValue().getValueText() }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ConstantReadAccess" }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import internal.AST
|
||||
private import internal.TreeSitter
|
||||
|
||||
@@ -7,7 +8,12 @@ private import internal.TreeSitter
|
||||
*
|
||||
* This is the root QL class for all expressions.
|
||||
*/
|
||||
class Expr extends Stmt, TExpr { }
|
||||
class Expr extends Stmt, TExpr {
|
||||
/** Gets the textual (constant) value of this expression, if any. */
|
||||
string getValueText() {
|
||||
forex(CfgNodes::ExprCfgNode n | n = this.getAControlFlowNode() | result = n.getValueText())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to the current object. For example:
|
||||
|
||||
@@ -16,7 +16,7 @@ class Literal extends Expr, TLiteral {
|
||||
* For complex literals, such as arrays, hashes, and strings with
|
||||
* interpolations, this predicate has no result.
|
||||
*/
|
||||
string getValueText() { none() }
|
||||
override string getValueText() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,9 +7,9 @@ private import internal.TreeSitter
|
||||
private import internal.Variable
|
||||
|
||||
/** A variable declared in a scope. */
|
||||
class Variable extends TVariable {
|
||||
class Variable instanceof VariableImpl {
|
||||
/** Gets the name of this variable. */
|
||||
string getName() { none() }
|
||||
final string getName() { result = super.getNameImpl() }
|
||||
|
||||
/** Holds if the name of this variable is `name`. */
|
||||
final predicate hasName(string name) { this.getName() = name }
|
||||
@@ -18,10 +18,12 @@ class Variable extends TVariable {
|
||||
final string toString() { result = this.getName() }
|
||||
|
||||
/** Gets the location of this variable. */
|
||||
Location getLocation() { none() }
|
||||
final Location getLocation() { result = super.getLocationImpl() }
|
||||
|
||||
/** Gets the scope this variable is declared in. */
|
||||
Scope getDeclaringScope() { none() }
|
||||
final Scope getDeclaringScope() {
|
||||
toGenerated(result) = this.(VariableReal).getDeclaringScopeImpl()
|
||||
}
|
||||
|
||||
/** Gets an access to this variable. */
|
||||
VariableAccess getAnAccess() { result.getVariable() = this }
|
||||
@@ -29,10 +31,10 @@ class Variable extends TVariable {
|
||||
|
||||
/** A local variable. */
|
||||
class LocalVariable extends Variable, TLocalVariable {
|
||||
override LocalVariableAccess getAnAccess() { none() }
|
||||
override LocalVariableAccess getAnAccess() { result.getVariable() = this }
|
||||
|
||||
/** Gets the access where this local variable is first introduced. */
|
||||
VariableAccess getDefiningAccess() { none() }
|
||||
VariableAccess getDefiningAccess() { result = this.(LocalVariableReal).getDefiningAccessImpl() }
|
||||
|
||||
/**
|
||||
* Holds if this variable is captured. For example in
|
||||
@@ -52,12 +54,12 @@ class LocalVariable extends Variable, TLocalVariable {
|
||||
}
|
||||
|
||||
/** A global variable. */
|
||||
class GlobalVariable extends VariableReal, TGlobalVariable instanceof GlobalVariable::Range {
|
||||
class GlobalVariable extends Variable instanceof GlobalVariableImpl {
|
||||
final override GlobalVariableAccess getAnAccess() { result.getVariable() = this }
|
||||
}
|
||||
|
||||
/** An instance variable. */
|
||||
class InstanceVariable extends VariableReal, TInstanceVariable instanceof InstanceVariable::Range {
|
||||
class InstanceVariable extends Variable instanceof InstanceVariableImpl {
|
||||
/** Holds is this variable is a class instance variable. */
|
||||
final predicate isClassInstanceVariable() { super.isClassInstanceVariable() }
|
||||
|
||||
@@ -65,14 +67,14 @@ class InstanceVariable extends VariableReal, TInstanceVariable instanceof Instan
|
||||
}
|
||||
|
||||
/** A class variable. */
|
||||
class ClassVariable extends VariableReal, TClassVariable instanceof ClassVariable::Range {
|
||||
class ClassVariable extends Variable instanceof ClassVariableImpl {
|
||||
final override ClassVariableAccess getAnAccess() { result.getVariable() = this }
|
||||
}
|
||||
|
||||
/** An access to a variable. */
|
||||
class VariableAccess extends Expr, TVariableAccess {
|
||||
class VariableAccess extends Expr instanceof VariableAccessImpl {
|
||||
/** Gets the variable this identifier refers to. */
|
||||
final Variable getVariable() { result = this.(VariableAccessImpl).getVariableImpl() }
|
||||
final Variable getVariable() { result = super.getVariableImpl() }
|
||||
|
||||
/**
|
||||
* Holds if this access is a write access belonging to the explicit
|
||||
@@ -106,6 +108,8 @@ class VariableAccess extends Expr, TVariableAccess {
|
||||
* as is the first access to `e`.
|
||||
*/
|
||||
predicate isImplicitWrite() { implicitWriteAccess(toGenerated(this)) }
|
||||
|
||||
final override string toString() { result = VariableAccessImpl.super.toString() }
|
||||
}
|
||||
|
||||
/** An access to a variable where the value is updated. */
|
||||
@@ -122,7 +126,7 @@ class VariableReadAccess extends VariableAccess {
|
||||
}
|
||||
|
||||
/** An access to a local variable. */
|
||||
class LocalVariableAccess extends VariableAccess, TLocalVariableAccess {
|
||||
class LocalVariableAccess extends VariableAccess instanceof LocalVariableAccessImpl {
|
||||
final override string getAPrimaryQlClass() { result = "LocalVariableAccess" }
|
||||
|
||||
/**
|
||||
@@ -150,7 +154,7 @@ class LocalVariableWriteAccess extends LocalVariableAccess, VariableWriteAccess
|
||||
class LocalVariableReadAccess extends LocalVariableAccess, VariableReadAccess { }
|
||||
|
||||
/** An access to a global variable. */
|
||||
class GlobalVariableAccess extends VariableAccess, TGlobalVariableAccess {
|
||||
class GlobalVariableAccess extends VariableAccess instanceof GlobalVariableAccessImpl {
|
||||
final override string getAPrimaryQlClass() { result = "GlobalVariableAccess" }
|
||||
}
|
||||
|
||||
@@ -161,7 +165,7 @@ class GlobalVariableWriteAccess extends GlobalVariableAccess, VariableWriteAcces
|
||||
class GlobalVariableReadAccess extends GlobalVariableAccess, VariableReadAccess { }
|
||||
|
||||
/** An access to an instance variable. */
|
||||
class InstanceVariableAccess extends VariableAccess, TInstanceVariableAccess {
|
||||
class InstanceVariableAccess extends VariableAccess instanceof InstanceVariableAccessImpl {
|
||||
final override string getAPrimaryQlClass() { result = "InstanceVariableAccess" }
|
||||
}
|
||||
|
||||
@@ -172,7 +176,7 @@ class InstanceVariableWriteAccess extends InstanceVariableAccess, VariableWriteA
|
||||
class InstanceVariableReadAccess extends InstanceVariableAccess, VariableReadAccess { }
|
||||
|
||||
/** An access to a class variable. */
|
||||
class ClassVariableAccess extends VariableAccess, TClassVariableAccess {
|
||||
class ClassVariableAccess extends VariableAccess instanceof ClassVariableAccessRealImpl {
|
||||
final override string getAPrimaryQlClass() { result = "ClassVariableAccess" }
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ predicate isScopeResolutionMethodCall(Ruby::ScopeResolution g, Ruby::Identifier
|
||||
not exists(Ruby::Call c | c.getMethod() = g)
|
||||
}
|
||||
|
||||
abstract class CallImpl extends Call {
|
||||
abstract class CallImpl extends Expr, TCall {
|
||||
abstract AstNode getArgumentImpl(int n);
|
||||
|
||||
/**
|
||||
@@ -27,10 +27,12 @@ abstract class CallImpl extends Call {
|
||||
abstract int getNumberOfArgumentsImpl();
|
||||
}
|
||||
|
||||
abstract class MethodCallImpl extends CallImpl, MethodCall {
|
||||
abstract class MethodCallImpl extends CallImpl, TMethodCall {
|
||||
abstract AstNode getReceiverImpl();
|
||||
|
||||
abstract string getMethodNameImpl();
|
||||
|
||||
abstract Block getBlockImpl();
|
||||
}
|
||||
|
||||
class MethodCallSynth extends MethodCallImpl, TMethodCallSynth {
|
||||
@@ -47,6 +49,8 @@ class MethodCallSynth extends MethodCallImpl, TMethodCallSynth {
|
||||
final override AstNode getArgumentImpl(int n) { synthChild(this, n + 1, result) and n >= 0 }
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { this = TMethodCallSynth(_, _, _, _, result) }
|
||||
|
||||
final override Block getBlockImpl() { none() }
|
||||
}
|
||||
|
||||
class IdentifierMethodCall extends MethodCallImpl, TIdentifierMethodCall {
|
||||
@@ -61,6 +65,8 @@ class IdentifierMethodCall extends MethodCallImpl, TIdentifierMethodCall {
|
||||
final override Expr getArgumentImpl(int n) { none() }
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { result = 0 }
|
||||
|
||||
final override Block getBlockImpl() { none() }
|
||||
}
|
||||
|
||||
class ScopeResolutionMethodCall extends MethodCallImpl, TScopeResolutionMethodCall {
|
||||
@@ -76,6 +82,8 @@ class ScopeResolutionMethodCall extends MethodCallImpl, TScopeResolutionMethodCa
|
||||
final override Expr getArgumentImpl(int n) { none() }
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { result = 0 }
|
||||
|
||||
final override Block getBlockImpl() { none() }
|
||||
}
|
||||
|
||||
class RegularMethodCall extends MethodCallImpl, TRegularMethodCall {
|
||||
@@ -114,7 +122,7 @@ class RegularMethodCall extends MethodCallImpl, TRegularMethodCall {
|
||||
count(g.getArguments().getChild(_)) + count(g.getMethod().(Ruby::ArgumentList).getChild(_))
|
||||
}
|
||||
|
||||
final override Block getBlock() { toGenerated(result) = g.getBlock() }
|
||||
final override Block getBlockImpl() { toGenerated(result) = g.getBlock() }
|
||||
}
|
||||
|
||||
class ElementReferenceImpl extends MethodCallImpl, TElementReference {
|
||||
@@ -129,9 +137,13 @@ class ElementReferenceImpl extends MethodCallImpl, TElementReference {
|
||||
final override int getNumberOfArgumentsImpl() { result = count(g.getChild(_)) }
|
||||
|
||||
final override string getMethodNameImpl() { result = "[]" }
|
||||
|
||||
final override Block getBlockImpl() { none() }
|
||||
}
|
||||
|
||||
class TokenSuperCall extends SuperCall, MethodCallImpl, TTokenSuperCall {
|
||||
abstract class SuperCallImpl extends MethodCallImpl, TSuperCall { }
|
||||
|
||||
class TokenSuperCall extends SuperCallImpl, TTokenSuperCall {
|
||||
private Ruby::Super g;
|
||||
|
||||
TokenSuperCall() { this = TTokenSuperCall(g) }
|
||||
@@ -143,9 +155,11 @@ class TokenSuperCall extends SuperCall, MethodCallImpl, TTokenSuperCall {
|
||||
final override Expr getArgumentImpl(int n) { none() }
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { result = 0 }
|
||||
|
||||
final override Block getBlockImpl() { none() }
|
||||
}
|
||||
|
||||
class RegularSuperCall extends SuperCall, MethodCallImpl, TRegularSuperCall {
|
||||
class RegularSuperCall extends SuperCallImpl, TRegularSuperCall {
|
||||
private Ruby::Call g;
|
||||
|
||||
RegularSuperCall() { this = TRegularSuperCall(g) }
|
||||
@@ -158,10 +172,14 @@ class RegularSuperCall extends SuperCall, MethodCallImpl, TRegularSuperCall {
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { result = count(g.getArguments().getChild(_)) }
|
||||
|
||||
final override Block getBlock() { toGenerated(result) = g.getBlock() }
|
||||
final override Block getBlockImpl() { toGenerated(result) = g.getBlock() }
|
||||
}
|
||||
|
||||
class YieldCallImpl extends CallImpl, YieldCall {
|
||||
class YieldCallImpl extends CallImpl, TYieldCall {
|
||||
Ruby::Yield g;
|
||||
|
||||
YieldCallImpl() { this = TYieldCall(g) }
|
||||
|
||||
final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getChild().getChild(n) }
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { result = count(g.getChild().getChild(_)) }
|
||||
|
||||
@@ -220,7 +220,7 @@ private string resolveScopeExpr(ConstantReadAccess c, int priority) {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private string resolveScopeExprConstantReadAccess(ConstantReadAccess c, int priority, string name) {
|
||||
result = resolveScopeExpr(c.getScopeExpr(), priority) and
|
||||
name = c.getName()
|
||||
|
||||
@@ -21,6 +21,8 @@ abstract class UnaryOperationImpl extends OperationImpl, MethodCallImpl, TUnaryO
|
||||
final override Expr getArgumentImpl(int n) { none() }
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { result = 0 }
|
||||
|
||||
final override Block getBlockImpl() { none() }
|
||||
}
|
||||
|
||||
class UnaryOperationGenerated extends UnaryOperationImpl {
|
||||
@@ -77,6 +79,8 @@ abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinar
|
||||
final override Expr getArgumentImpl(int n) { n = 0 and result = this.getRightOperandImpl() }
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { result = 1 }
|
||||
|
||||
final override Block getBlockImpl() { none() }
|
||||
}
|
||||
|
||||
class BinaryOperationReal extends BinaryOperationImpl {
|
||||
|
||||
@@ -34,10 +34,10 @@ module Ruby {
|
||||
/** A token. */
|
||||
class Token extends @ruby_token, AstNode {
|
||||
/** Gets the value of this token. */
|
||||
string getValue() { ruby_tokeninfo(this, _, _, _, result, _) }
|
||||
string getValue() { ruby_tokeninfo(this, _, result, _) }
|
||||
|
||||
/** Gets the location of this token. */
|
||||
override Location getLocation() { ruby_tokeninfo(this, _, _, _, _, result) }
|
||||
override Location getLocation() { ruby_tokeninfo(this, _, _, result) }
|
||||
|
||||
/** Gets a string representation of this element. */
|
||||
override string toString() { result = getValue() }
|
||||
@@ -1875,10 +1875,10 @@ module Erb {
|
||||
/** A token. */
|
||||
class Token extends @erb_token, AstNode {
|
||||
/** Gets the value of this token. */
|
||||
string getValue() { erb_tokeninfo(this, _, _, _, result, _) }
|
||||
string getValue() { erb_tokeninfo(this, _, result, _) }
|
||||
|
||||
/** Gets the location of this token. */
|
||||
override Location getLocation() { erb_tokeninfo(this, _, _, _, _, result) }
|
||||
override Location getLocation() { erb_tokeninfo(this, _, _, result) }
|
||||
|
||||
/** Gets a string representation of this element. */
|
||||
override string toString() { result = getValue() }
|
||||
|
||||
@@ -282,13 +282,13 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
predicate access(Ruby::Identifier access, VariableReal::Range variable) {
|
||||
predicate access(Ruby::Identifier access, VariableReal variable) {
|
||||
exists(string name |
|
||||
variable.getName() = name and
|
||||
variable.getNameImpl() = name and
|
||||
name = access.getValue()
|
||||
|
|
||||
variable.getDeclaringScope() = scopeOf(access) and
|
||||
not access.getLocation().strictlyBefore(variable.getLocation()) and
|
||||
variable.getDeclaringScopeImpl() = scopeOf(access) and
|
||||
not access.getLocation().strictlyBefore(variable.getLocationImpl()) and
|
||||
// In case of overlapping parameter names, later parameters should not
|
||||
// be considered accesses to the first parameter
|
||||
if parameterAssignment(_, _, access)
|
||||
@@ -296,7 +296,7 @@ private module Cached {
|
||||
else any()
|
||||
or
|
||||
exists(Scope::Range declScope |
|
||||
variable.getDeclaringScope() = declScope and
|
||||
variable.getDeclaringScopeImpl() = declScope and
|
||||
inherits(scopeOf(access), name, declScope)
|
||||
)
|
||||
)
|
||||
@@ -366,126 +366,117 @@ private predicate inherits(Scope::Range scope, string name, Scope::Range outer)
|
||||
)
|
||||
}
|
||||
|
||||
class TVariableReal = TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal;
|
||||
abstract class VariableImpl extends TVariable {
|
||||
abstract string getNameImpl();
|
||||
|
||||
module VariableReal {
|
||||
class Range extends TVariableReal {
|
||||
abstract string getName();
|
||||
final string toString() { result = this.getNameImpl() }
|
||||
|
||||
string toString() { result = this.getName() }
|
||||
|
||||
abstract Location getLocation();
|
||||
|
||||
abstract Scope::Range getDeclaringScope();
|
||||
}
|
||||
abstract Location getLocationImpl();
|
||||
}
|
||||
|
||||
class TVariableReal = TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal;
|
||||
|
||||
class TLocalVariable = TLocalVariableReal or TLocalVariableSynth;
|
||||
|
||||
module LocalVariable {
|
||||
class Range extends VariableReal::Range, TLocalVariableReal {
|
||||
private Scope::Range scope;
|
||||
private string name;
|
||||
private Ruby::Identifier i;
|
||||
/**
|
||||
* This class only exists to avoid negative recursion warnings. Ideally,
|
||||
* we would use `VariableImpl` directly, but that results in incorrect
|
||||
* negative recursion warnings. Adding new root-defs for the predicates
|
||||
* below works around this.
|
||||
*/
|
||||
abstract class VariableReal extends TVariableReal {
|
||||
abstract string getNameImpl();
|
||||
|
||||
Range() { this = TLocalVariableReal(scope, name, i) }
|
||||
abstract Location getLocationImpl();
|
||||
|
||||
final override string getName() { result = name }
|
||||
abstract Scope::Range getDeclaringScopeImpl();
|
||||
|
||||
final override Location getLocation() { result = i.getLocation() }
|
||||
|
||||
final override Scope::Range getDeclaringScope() { result = scope }
|
||||
|
||||
final VariableAccess getDefiningAccess() { toGenerated(result) = i }
|
||||
}
|
||||
final string toString() { result = this.getNameImpl() }
|
||||
}
|
||||
|
||||
class VariableReal extends Variable, TVariableReal instanceof VariableReal::Range {
|
||||
final override string getName() { result = VariableReal::Range.super.getName() }
|
||||
// Convert extensions of `VariableReal` into extensions of `VariableImpl`
|
||||
private class VariableRealAdapter extends VariableImpl, TVariableReal instanceof VariableReal {
|
||||
final override string getNameImpl() { result = VariableReal.super.getNameImpl() }
|
||||
|
||||
final override Location getLocation() { result = VariableReal::Range.super.getLocation() }
|
||||
|
||||
final override Scope getDeclaringScope() {
|
||||
toGenerated(result) = VariableReal::Range.super.getDeclaringScope()
|
||||
}
|
||||
final override Location getLocationImpl() { result = VariableReal.super.getLocationImpl() }
|
||||
}
|
||||
|
||||
class LocalVariableReal extends VariableReal, LocalVariable, TLocalVariableReal instanceof LocalVariable::Range {
|
||||
final override LocalVariableAccessReal getAnAccess() { result.getVariable() = this }
|
||||
class LocalVariableReal extends VariableReal, TLocalVariableReal {
|
||||
private Scope::Range scope;
|
||||
private string name;
|
||||
private Ruby::Identifier i;
|
||||
|
||||
final override VariableAccess getDefiningAccess() {
|
||||
result = LocalVariable::Range.super.getDefiningAccess()
|
||||
}
|
||||
LocalVariableReal() { this = TLocalVariableReal(scope, name, i) }
|
||||
|
||||
final override string getNameImpl() { result = name }
|
||||
|
||||
final override Location getLocationImpl() { result = i.getLocation() }
|
||||
|
||||
final override Scope::Range getDeclaringScopeImpl() { result = scope }
|
||||
|
||||
final VariableAccess getDefiningAccessImpl() { toGenerated(result) = i }
|
||||
}
|
||||
|
||||
class LocalVariableSynth extends LocalVariable, TLocalVariableSynth {
|
||||
class LocalVariableSynth extends VariableImpl, TLocalVariableSynth {
|
||||
private AstNode n;
|
||||
private int i;
|
||||
|
||||
LocalVariableSynth() { this = TLocalVariableSynth(n, i) }
|
||||
|
||||
final override string getName() {
|
||||
final override string getNameImpl() {
|
||||
exists(int level | level = desugarLevel(n) |
|
||||
if level > 0 then result = "__synth__" + i + "__" + level else result = "__synth__" + i
|
||||
)
|
||||
}
|
||||
|
||||
final override Location getLocation() { result = n.getLocation() }
|
||||
|
||||
final override Scope getDeclaringScope() { none() } // not relevant for synthesized variables
|
||||
final override Location getLocationImpl() { result = n.getLocation() }
|
||||
}
|
||||
|
||||
module GlobalVariable {
|
||||
class Range extends VariableReal::Range, TGlobalVariable {
|
||||
private string name;
|
||||
class GlobalVariableImpl extends VariableReal, TGlobalVariable {
|
||||
private string name;
|
||||
|
||||
Range() { this = TGlobalVariable(name) }
|
||||
GlobalVariableImpl() { this = TGlobalVariable(name) }
|
||||
|
||||
final override string getName() { result = name }
|
||||
final override string getNameImpl() { result = name }
|
||||
|
||||
final override Location getLocation() { none() }
|
||||
final override Location getLocationImpl() { none() }
|
||||
|
||||
final override Scope::Range getDeclaringScope() { none() }
|
||||
}
|
||||
final override Scope::Range getDeclaringScopeImpl() { none() }
|
||||
}
|
||||
|
||||
module InstanceVariable {
|
||||
class Range extends VariableReal::Range, TInstanceVariable {
|
||||
private ModuleBase::Range scope;
|
||||
private boolean instance;
|
||||
private string name;
|
||||
private Ruby::AstNode decl;
|
||||
class InstanceVariableImpl extends VariableReal, TInstanceVariable {
|
||||
private ModuleBase::Range scope;
|
||||
private boolean instance;
|
||||
private string name;
|
||||
private Ruby::AstNode decl;
|
||||
|
||||
Range() { this = TInstanceVariable(scope, name, instance, decl) }
|
||||
InstanceVariableImpl() { this = TInstanceVariable(scope, name, instance, decl) }
|
||||
|
||||
final override string getName() { result = name }
|
||||
final override string getNameImpl() { result = name }
|
||||
|
||||
final predicate isClassInstanceVariable() { instance = false }
|
||||
final predicate isClassInstanceVariable() { instance = false }
|
||||
|
||||
final override Location getLocation() { result = decl.getLocation() }
|
||||
final override Location getLocationImpl() { result = decl.getLocation() }
|
||||
|
||||
final override Scope::Range getDeclaringScope() { result = scope }
|
||||
}
|
||||
final override Scope::Range getDeclaringScopeImpl() { result = scope }
|
||||
}
|
||||
|
||||
module ClassVariable {
|
||||
class Range extends VariableReal::Range, TClassVariable {
|
||||
private ModuleBase::Range scope;
|
||||
private string name;
|
||||
private Ruby::AstNode decl;
|
||||
class ClassVariableImpl extends VariableReal, TClassVariable {
|
||||
private ModuleBase::Range scope;
|
||||
private string name;
|
||||
private Ruby::AstNode decl;
|
||||
|
||||
Range() { this = TClassVariable(scope, name, decl) }
|
||||
ClassVariableImpl() { this = TClassVariable(scope, name, decl) }
|
||||
|
||||
final override string getName() { result = name }
|
||||
final override string getNameImpl() { result = name }
|
||||
|
||||
final override Location getLocation() { result = decl.getLocation() }
|
||||
final override Location getLocationImpl() { result = decl.getLocation() }
|
||||
|
||||
final override Scope::Range getDeclaringScope() { result = scope }
|
||||
}
|
||||
final override Scope::Range getDeclaringScopeImpl() { result = scope }
|
||||
}
|
||||
|
||||
abstract class VariableAccessImpl extends VariableAccess {
|
||||
abstract Variable getVariableImpl();
|
||||
abstract class VariableAccessImpl extends Expr, TVariableAccess {
|
||||
abstract VariableImpl getVariableImpl();
|
||||
}
|
||||
|
||||
module LocalVariableAccess {
|
||||
@@ -505,8 +496,9 @@ class TVariableAccessReal =
|
||||
TLocalVariableAccessReal or TGlobalVariableAccess or TInstanceVariableAccess or
|
||||
TClassVariableAccess;
|
||||
|
||||
private class LocalVariableAccessReal extends VariableAccessImpl, LocalVariableAccess,
|
||||
TLocalVariableAccessReal {
|
||||
abstract class LocalVariableAccessImpl extends VariableAccessImpl, TLocalVariableAccess { }
|
||||
|
||||
private class LocalVariableAccessReal extends LocalVariableAccessImpl, TLocalVariableAccessReal {
|
||||
private Ruby::Identifier g;
|
||||
private LocalVariable v;
|
||||
|
||||
@@ -517,8 +509,7 @@ private class LocalVariableAccessReal extends VariableAccessImpl, LocalVariableA
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class LocalVariableAccessSynth extends VariableAccessImpl, LocalVariableAccess,
|
||||
TLocalVariableAccessSynth {
|
||||
private class LocalVariableAccessSynth extends LocalVariableAccessImpl, TLocalVariableAccessSynth {
|
||||
private LocalVariable v;
|
||||
|
||||
LocalVariableAccessSynth() { this = TLocalVariableAccessSynth(_, _, v) }
|
||||
@@ -529,11 +520,12 @@ private class LocalVariableAccessSynth extends VariableAccessImpl, LocalVariable
|
||||
}
|
||||
|
||||
module GlobalVariableAccess {
|
||||
predicate range(Ruby::GlobalVariable n, GlobalVariable v) { n.getValue() = v.getName() }
|
||||
predicate range(Ruby::GlobalVariable n, GlobalVariableImpl v) { n.getValue() = v.getNameImpl() }
|
||||
}
|
||||
|
||||
private class GlobalVariableAccessReal extends GlobalVariableAccess, VariableAccessImpl,
|
||||
TGlobalVariableAccessReal {
|
||||
abstract class GlobalVariableAccessImpl extends VariableAccessImpl, TGlobalVariableAccess { }
|
||||
|
||||
private class GlobalVariableAccessReal extends GlobalVariableAccessImpl, TGlobalVariableAccessReal {
|
||||
private Ruby::GlobalVariable g;
|
||||
private GlobalVariable v;
|
||||
|
||||
@@ -544,8 +536,7 @@ private class GlobalVariableAccessReal extends GlobalVariableAccess, VariableAcc
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class GlobalVariableAccessSynth extends GlobalVariableAccess, VariableAccessImpl,
|
||||
TGlobalVariableAccessSynth {
|
||||
private class GlobalVariableAccessSynth extends GlobalVariableAccessImpl, TGlobalVariableAccessSynth {
|
||||
private GlobalVariable v;
|
||||
|
||||
GlobalVariableAccessSynth() { this = TGlobalVariableAccessSynth(_, _, v) }
|
||||
@@ -559,7 +550,9 @@ module InstanceVariableAccess {
|
||||
predicate range(Ruby::InstanceVariable n, InstanceVariable v) { instanceVariableAccess(n, v) }
|
||||
}
|
||||
|
||||
private class InstanceVariableAccessReal extends InstanceVariableAccess, VariableAccessImpl,
|
||||
abstract class InstanceVariableAccessImpl extends VariableAccessImpl, TInstanceVariableAccess { }
|
||||
|
||||
private class InstanceVariableAccessReal extends InstanceVariableAccessImpl,
|
||||
TInstanceVariableAccessReal {
|
||||
private Ruby::InstanceVariable g;
|
||||
private InstanceVariable v;
|
||||
@@ -571,7 +564,7 @@ private class InstanceVariableAccessReal extends InstanceVariableAccess, Variabl
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class InstanceVariableAccessSynth extends InstanceVariableAccess, VariableAccessImpl,
|
||||
private class InstanceVariableAccessSynth extends InstanceVariableAccessImpl,
|
||||
TInstanceVariableAccessSynth {
|
||||
private InstanceVariable v;
|
||||
|
||||
@@ -586,8 +579,9 @@ module ClassVariableAccess {
|
||||
predicate range(Ruby::ClassVariable n, ClassVariable v) { classVariableAccess(n, v) }
|
||||
}
|
||||
|
||||
private class ClassVariableAccessReal extends ClassVariableAccess, VariableAccessImpl,
|
||||
TClassVariableAccessReal {
|
||||
abstract class ClassVariableAccessRealImpl extends VariableAccessImpl, TClassVariableAccess { }
|
||||
|
||||
private class ClassVariableAccessReal extends ClassVariableAccessRealImpl, TClassVariableAccessReal {
|
||||
private Ruby::ClassVariable g;
|
||||
private ClassVariable v;
|
||||
|
||||
@@ -598,7 +592,7 @@ private class ClassVariableAccessReal extends ClassVariableAccess, VariableAcces
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class ClassVariableAccessSynth extends ClassVariableAccess, VariableAccessImpl,
|
||||
private class ClassVariableAccessSynth extends ClassVariableAccessRealImpl,
|
||||
TClassVariableAccessSynth {
|
||||
private ClassVariable v;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.controlflow.BasicBlocks
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
private import ControlFlowGraph
|
||||
private import internal.ControlFlowGraphImpl
|
||||
private import internal.Splitting
|
||||
@@ -98,6 +99,16 @@ class ExprCfgNode extends AstCfgNode {
|
||||
|
||||
/** Gets the underlying expression. */
|
||||
Expr getExpr() { result = e }
|
||||
|
||||
private ExprCfgNode getSource() {
|
||||
exists(Ssa::WriteDefinition def |
|
||||
def.assigns(result) and
|
||||
this = def.getARead()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual (constant) value of this expression, if any. */
|
||||
string getValueText() { result = this.getSource().getValueText() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a return-like statement. */
|
||||
@@ -164,7 +175,19 @@ abstract private class ExprChildMapping extends Expr {
|
||||
|
||||
/** Provides classes for control-flow nodes that wrap AST expressions. */
|
||||
module ExprNodes {
|
||||
// TODO: Add more classes
|
||||
private class LiteralChildMapping extends ExprChildMapping, Literal {
|
||||
override predicate relevantChild(Expr e) { none() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an `ArrayLiteral` AST expression. */
|
||||
class LiteralCfgNode extends ExprCfgNode {
|
||||
override LiteralChildMapping e;
|
||||
|
||||
override Literal getExpr() { result = super.getExpr() }
|
||||
|
||||
override string getValueText() { result = e.getValueText() }
|
||||
}
|
||||
|
||||
private class AssignExprChildMapping extends ExprChildMapping, AssignExpr {
|
||||
override predicate relevantChild(Expr e) { e = this.getAnOperand() }
|
||||
}
|
||||
@@ -209,6 +232,49 @@ module ExprNodes {
|
||||
|
||||
/** Gets the right operand of this binary operation. */
|
||||
final ExprCfgNode getRightOperand() { e.hasCfgChild(bo.getRightOperand(), this, result) }
|
||||
|
||||
final override string getValueText() {
|
||||
exists(string left, string right, string op |
|
||||
left = this.getLeftOperand().getValueText() and
|
||||
right = this.getRightOperand().getValueText() and
|
||||
op = this.getExpr().getOperator()
|
||||
|
|
||||
op = "+" and
|
||||
(
|
||||
result = (left.toInt() + right.toInt()).toString()
|
||||
or
|
||||
not (exists(left.toInt()) and exists(right.toInt())) and
|
||||
result = (left.toFloat() + right.toFloat()).toString()
|
||||
or
|
||||
not (exists(left.toFloat()) and exists(right.toFloat())) and
|
||||
result = left + right
|
||||
)
|
||||
or
|
||||
op = "-" and
|
||||
(
|
||||
result = (left.toInt() - right.toInt()).toString()
|
||||
or
|
||||
not (exists(left.toInt()) and exists(right.toInt())) and
|
||||
result = (left.toFloat() - right.toFloat()).toString()
|
||||
)
|
||||
or
|
||||
op = "*" and
|
||||
(
|
||||
result = (left.toInt() * right.toInt()).toString()
|
||||
or
|
||||
not (exists(left.toInt()) and exists(right.toInt())) and
|
||||
result = (left.toFloat() * right.toFloat()).toString()
|
||||
)
|
||||
or
|
||||
op = "/" and
|
||||
(
|
||||
result = (left.toInt() / right.toInt()).toString()
|
||||
or
|
||||
not (exists(left.toInt()) and exists(right.toInt())) and
|
||||
result = (left.toFloat() / right.toFloat()).toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockArgumentChildMapping extends ExprChildMapping, BlockArgument {
|
||||
|
||||
@@ -180,38 +180,42 @@ private module Cached {
|
||||
|
||||
cached
|
||||
CfgScope getTarget(CfgNodes::ExprNodes::CallCfgNode call) {
|
||||
exists(string method |
|
||||
exists(Module tp |
|
||||
instanceMethodCall(call, tp, method) and
|
||||
result = lookupMethod(tp, method) and
|
||||
if result.(Method).isPrivate()
|
||||
then
|
||||
exists(Self self |
|
||||
self = call.getReceiver().getExpr() and
|
||||
pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) =
|
||||
pragma[only_bind_out](result.getEnclosingModule().getModule())
|
||||
) and
|
||||
// For now, we restrict the scope of top-level declarations to their file.
|
||||
// This may remove some plausible targets, but also removes a lot of
|
||||
// implausible targets
|
||||
if result.getEnclosingModule() instanceof Toplevel
|
||||
then result.getFile() = call.getFile()
|
||||
// Temporarily disable operation resolution (due to bad performance)
|
||||
not call.getExpr() instanceof Operation and
|
||||
(
|
||||
exists(string method |
|
||||
exists(Module tp |
|
||||
instanceMethodCall(call, tp, method) and
|
||||
result = lookupMethod(tp, method) and
|
||||
if result.(Method).isPrivate()
|
||||
then
|
||||
exists(Self self |
|
||||
self = call.getReceiver().getExpr() and
|
||||
pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) =
|
||||
pragma[only_bind_out](result.getEnclosingModule().getModule())
|
||||
) and
|
||||
// For now, we restrict the scope of top-level declarations to their file.
|
||||
// This may remove some plausible targets, but also removes a lot of
|
||||
// implausible targets
|
||||
if result.getEnclosingModule() instanceof Toplevel
|
||||
then result.getFile() = call.getFile()
|
||||
else any()
|
||||
else any()
|
||||
else any()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::LocalSourceNode sourceNode |
|
||||
methodCall(call, sourceNode, method) and
|
||||
sourceNode = trackSingletonMethod(result, method)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::LocalSourceNode sourceNode |
|
||||
methodCall(call, sourceNode, method) and
|
||||
sourceNode = trackSingletonMethod(result, method)
|
||||
exists(Module superClass, string method |
|
||||
superCall(call, superClass, method) and
|
||||
result = lookupMethod(superClass, method)
|
||||
)
|
||||
or
|
||||
result = yieldCall(call)
|
||||
)
|
||||
or
|
||||
exists(Module superClass, string method |
|
||||
superCall(call, superClass, method) and
|
||||
result = lookupMethod(superClass, method)
|
||||
)
|
||||
or
|
||||
result = yieldCall(call)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +436,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
* Holds if `e` is an `ExprNode` that may be returned by a call to `c`.
|
||||
*/
|
||||
predicate exprNodeReturnedFrom(DataFlow::ExprNode e, Callable c) {
|
||||
exists(ReturnNode r |
|
||||
exists(ReturningNode r |
|
||||
r.getEnclosingCallable().asCallable() = c and
|
||||
(
|
||||
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
|
||||
|
||||
@@ -46,18 +46,19 @@ module LocalFlow {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the SSA definition node corresponding to parameter `p`. */
|
||||
SsaDefinitionNode getParameterDefNode(NamedParameter p) {
|
||||
exists(BasicBlock bb, int i |
|
||||
bb.getNode(i).getNode() = p.getDefiningAccess() and
|
||||
result.getDefinition().definesAt(_, bb, i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
|
||||
* SSA definition `def.
|
||||
* SSA definition `def`.
|
||||
*/
|
||||
predicate localSsaFlowStep(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
|
||||
// Flow from parameter into SSA definition
|
||||
exists(BasicBlock bb, int i |
|
||||
bb.getNode(i).getNode() =
|
||||
nodeFrom.(ParameterNode).getParameter().(NamedParameter).getDefiningAccess() and
|
||||
nodeTo.(SsaDefinitionNode).getDefinition().definesAt(_, bb, i)
|
||||
)
|
||||
or
|
||||
// Flow from assignment into SSA definition
|
||||
def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
|
||||
nodeTo.(SsaDefinitionNode).getDefinition() = def
|
||||
@@ -114,6 +115,12 @@ private module Cached {
|
||||
newtype TNode =
|
||||
TExprNode(CfgNodes::ExprCfgNode n) or
|
||||
TReturningNode(CfgNodes::ReturningCfgNode n) or
|
||||
TSynthReturnNode(CfgScope scope, ReturnKind kind) {
|
||||
exists(ReturningNode ret |
|
||||
ret.(NodeImpl).getCfgScope() = scope and
|
||||
ret.getKind() = kind
|
||||
)
|
||||
} or
|
||||
TSsaDefinitionNode(Ssa::Definition def) or
|
||||
TNormalParameterNode(Parameter p) { not p instanceof BlockParameter } or
|
||||
TSelfParameterNode(MethodBase m) or
|
||||
@@ -139,21 +146,14 @@ private module Cached {
|
||||
class TParameterNode =
|
||||
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode;
|
||||
|
||||
/**
|
||||
* This is the local flow predicate that is used as a building block in global
|
||||
* data flow. It excludes SSA flow through instance fields, as flow through fields
|
||||
* is handled by the global data-flow library, but includes various other steps
|
||||
* that are only relevant for global flow.
|
||||
*/
|
||||
cached
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::Definition def | LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo))
|
||||
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
|
||||
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
|
||||
or
|
||||
nodeTo.(ParameterNode).getParameter().(OptionalParameter).getDefaultValue() =
|
||||
nodeFrom.asExpr().getExpr()
|
||||
or
|
||||
nodeTo.(ParameterNode).getParameter().(KeywordParameter).getDefaultValue() =
|
||||
nodeFrom.asExpr().getExpr()
|
||||
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
|
||||
}
|
||||
|
||||
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
|
||||
or
|
||||
nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and
|
||||
nodeTo.asExpr().getExpr() instanceof Self
|
||||
@@ -186,12 +186,66 @@ private module Cached {
|
||||
) and
|
||||
nodeFrom.asExpr() = for.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the local flow predicate that is used as a building block in global
|
||||
* data flow.
|
||||
*/
|
||||
cached
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
|
||||
or
|
||||
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
|
||||
or
|
||||
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
|
||||
}
|
||||
|
||||
/** This is the local flow predicate that is exposed. */
|
||||
cached
|
||||
predicate isLocalSourceNode(Node n) { not simpleLocalFlowStep+(any(ExprNode e), n) }
|
||||
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
|
||||
localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
|
||||
or
|
||||
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true)
|
||||
}
|
||||
|
||||
/** This is the local flow predicate that is used in type tracking. */
|
||||
cached
|
||||
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
|
||||
localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
exists(NamedParameter p |
|
||||
defaultValueFlow(p, nodeFrom) and
|
||||
nodeTo = LocalFlow::getParameterDefNode(p)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate isLocalSourceNode(Node n) {
|
||||
n instanceof ParameterNode
|
||||
or
|
||||
// This case should not be needed once we have proper use-use flow
|
||||
// for `self`. At that point, the `self`s returned by `trackInstance`
|
||||
// in `DataFlowDispatch.qll` should refer to the post-update node,
|
||||
// and we can remove this case.
|
||||
n instanceof SelfArgumentNode
|
||||
or
|
||||
not localFlowStepTypeTracker+(any(Node e |
|
||||
e instanceof ExprNode
|
||||
or
|
||||
e instanceof ParameterNode
|
||||
), n)
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TContent = TTodoContent() // stub
|
||||
@@ -208,6 +262,8 @@ predicate nodeIsHidden(Node n) {
|
||||
n instanceof SummaryNode
|
||||
or
|
||||
n instanceof SummaryParameterNode
|
||||
or
|
||||
n instanceof SynthReturnNode
|
||||
}
|
||||
|
||||
/** An SSA definition, viewed as a node in a data flow graph. */
|
||||
@@ -234,7 +290,7 @@ class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
|
||||
* `ControlFlow::Node`s.
|
||||
*/
|
||||
class ReturningStatementNode extends NodeImpl, TReturningNode {
|
||||
private CfgNodes::ReturningCfgNode n;
|
||||
CfgNodes::ReturningCfgNode n;
|
||||
|
||||
ReturningStatementNode() { this = TReturningNode(n) }
|
||||
|
||||
@@ -436,6 +492,12 @@ private module ArgumentNodes {
|
||||
|
||||
import ArgumentNodes
|
||||
|
||||
/** A data-flow node that represents a value syntactically returned by a callable. */
|
||||
abstract class ReturningNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKind();
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a value returned by a callable. */
|
||||
abstract class ReturnNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
@@ -463,11 +525,9 @@ private module ReturnNodes {
|
||||
* A data-flow node that represents an expression returned by a callable,
|
||||
* either using an explict `return` statement or as the expression of a method body.
|
||||
*/
|
||||
class ExplicitReturnNode extends ReturnNode, ReturningStatementNode {
|
||||
private CfgNodes::ReturningCfgNode n;
|
||||
|
||||
class ExplicitReturnNode extends ReturningNode, ReturningStatementNode {
|
||||
ExplicitReturnNode() {
|
||||
isValid(this.getReturningNode()) and
|
||||
isValid(n) and
|
||||
n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
|
||||
n.getScope() instanceof Callable
|
||||
}
|
||||
@@ -479,7 +539,7 @@ private module ReturnNodes {
|
||||
}
|
||||
}
|
||||
|
||||
class ExprReturnNode extends ReturnNode, ExprNode {
|
||||
class ExprReturnNode extends ReturningNode, ExprNode {
|
||||
ExprReturnNode() {
|
||||
this.getExprNode().getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
|
||||
this.(NodeImpl).getCfgScope() instanceof Callable
|
||||
@@ -488,6 +548,34 @@ private module ReturnNodes {
|
||||
override ReturnKind getKind() { result instanceof NormalReturnKind }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic data-flow node for joining flow from different syntactic
|
||||
* returns into a single node.
|
||||
*
|
||||
* This node only exists to avoid computing the product of a large fan-in
|
||||
* with a large fan-out.
|
||||
*/
|
||||
class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
|
||||
private CfgScope scope;
|
||||
private ReturnKind kind;
|
||||
|
||||
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
|
||||
|
||||
/** Gets a syntactic return node that flows into this synthetic node. */
|
||||
ReturningNode getAnInput() {
|
||||
result.(NodeImpl).getCfgScope() = scope and
|
||||
result.getKind() = kind
|
||||
}
|
||||
|
||||
override ReturnKind getKind() { result = kind }
|
||||
|
||||
override CfgScope getCfgScope() { result = scope }
|
||||
|
||||
override Location getLocationImpl() { result = scope.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "return " + kind + " in " + scope }
|
||||
}
|
||||
|
||||
private class SummaryReturnNode extends SummaryNode, ReturnNode {
|
||||
private ReturnKind rk;
|
||||
|
||||
@@ -631,7 +719,7 @@ private import PostUpdateNodes
|
||||
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends Node {
|
||||
CastNode() { none() }
|
||||
CastNode() { this instanceof ReturningNode }
|
||||
}
|
||||
|
||||
class DataFlowExpr = CfgNodes::ExprCfgNode;
|
||||
|
||||
@@ -120,7 +120,7 @@ predicate hasLocalSource(Node sink, Node source) {
|
||||
or
|
||||
exists(Node mid |
|
||||
hasLocalSource(mid, source) and
|
||||
simpleLocalFlowStep(mid, sink)
|
||||
localFlowStepTypeTracker(mid, sink)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -136,13 +136,7 @@ ParameterNode parameterNode(Parameter p) { result.getParameter() = p }
|
||||
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
|
||||
* (intra-procedural) step.
|
||||
*/
|
||||
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo)
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true)
|
||||
}
|
||||
predicate localFlowStep = localFlowStepImpl/2;
|
||||
|
||||
/**
|
||||
* Holds if data flows from `source` to `sink` in zero or more local
|
||||
|
||||
@@ -28,7 +28,7 @@ private class ApplicationControllerAccess extends ConstantReadAccess {
|
||||
* class FooController < ActionController::Base
|
||||
* def delete_handler
|
||||
* uid = params[:id]
|
||||
* User.delete_all("id = ?", uid)
|
||||
* User.delete_by("id = ?", uid)
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
|
||||
@@ -68,16 +68,24 @@ private Expr sqlFragmentArgument(MethodCall call) {
|
||||
(
|
||||
methodName =
|
||||
[
|
||||
"delete_all", "destroy_all", "exists?", "find_by", "find_by_sql", "from", "group",
|
||||
"having", "joins", "lock", "not", "order", "pluck", "where"
|
||||
"delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
|
||||
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
|
||||
"group", "having", "joins", "lock", "not", "order", "pluck", "where", "rewhere", "select",
|
||||
"reselect", "update_all"
|
||||
] and
|
||||
result = call.getArgument(0)
|
||||
or
|
||||
methodName = "calculate" and result = call.getArgument(1)
|
||||
or
|
||||
methodName in ["average", "count", "maximum", "minimum", "sum"] and
|
||||
result = call.getArgument(0)
|
||||
or
|
||||
// This format was supported until Rails 2.3.8
|
||||
methodName = ["all", "find", "first", "last"] and
|
||||
result = call.getKeywordArgument("conditions")
|
||||
or
|
||||
methodName = "reload" and
|
||||
result = call.getKeywordArgument("lock")
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -118,7 +126,6 @@ class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMeth
|
||||
// The SQL fragment argument itself
|
||||
private Expr sqlFragmentExpr;
|
||||
|
||||
// TODO: `find` with `lock:` option also takes an SQL fragment
|
||||
PotentiallyUnsafeSqlExecutingMethodCall() {
|
||||
exists(Expr arg |
|
||||
arg = sqlFragmentArgument(this) and
|
||||
|
||||
@@ -36,13 +36,10 @@ private DataFlow::Node fileInstanceInstantiation() {
|
||||
result = API::getTopLevelMember("File").getAMethodCall("open")
|
||||
or
|
||||
// Calls to `Kernel.open` can yield `File` instances
|
||||
exists(KernelMethodCall c |
|
||||
c = result.asExpr().getExpr() and
|
||||
c.getMethodName() = "open" and
|
||||
// Assume that calls that don't invoke shell commands will instead open
|
||||
// a file.
|
||||
not pathArgSpawnsSubprocess(c.getArgument(0))
|
||||
)
|
||||
result.(KernelMethodCall).getMethodName() = "open" and
|
||||
// Assume that calls that don't invoke shell commands will instead open
|
||||
// a file.
|
||||
not pathArgSpawnsSubprocess(result.(KernelMethodCall).getArgument(0).asExpr().getExpr())
|
||||
}
|
||||
|
||||
private DataFlow::Node fileInstance() {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* Helper file that imports all HTTP clients.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.frameworks.http_clients.NetHTTP
|
||||
private import codeql.ruby.frameworks.http_clients.Excon
|
||||
private import codeql.ruby.frameworks.http_clients.Faraday
|
||||
private import codeql.ruby.frameworks.http_clients.RestClient
|
||||
12
ql/lib/codeql/ruby/frameworks/HttpClients.qll
Normal file
12
ql/lib/codeql/ruby/frameworks/HttpClients.qll
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Helper file that imports all HTTP clients.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.frameworks.http_clients.NetHttp
|
||||
private import codeql.ruby.frameworks.http_clients.Excon
|
||||
private import codeql.ruby.frameworks.http_clients.Faraday
|
||||
private import codeql.ruby.frameworks.http_clients.RestClient
|
||||
private import codeql.ruby.frameworks.http_clients.Httparty
|
||||
private import codeql.ruby.frameworks.http_clients.HttpClient
|
||||
private import codeql.ruby.frameworks.http_clients.OpenURI
|
||||
private import codeql.ruby.frameworks.http_clients.Typhoeus
|
||||
@@ -8,20 +8,27 @@ private import codeql.ruby.ApiGraphs
|
||||
* in every Ruby object. In addition, its module methods can be called by
|
||||
* providing a specific receiver as in `Kernel.exit`.
|
||||
*/
|
||||
class KernelMethodCall extends MethodCall {
|
||||
class KernelMethodCall extends DataFlow::CallNode {
|
||||
private MethodCall methodCall;
|
||||
|
||||
KernelMethodCall() {
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall(_).asExpr().getExpr()
|
||||
or
|
||||
// we assume that if there's no obvious target for this method call
|
||||
// and the method name matches a Kernel method, then it is a Kernel method call.
|
||||
// TODO: ApiGraphs should ideally handle this case
|
||||
not exists(this.(Call).getATarget()) and
|
||||
methodCall = this.asExpr().getExpr() and
|
||||
(
|
||||
this.getReceiver() instanceof Self and isPrivateKernelMethod(this.getMethodName())
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall(_)
|
||||
or
|
||||
isPublicKernelMethod(this.getMethodName())
|
||||
methodCall instanceof UnknownMethodCall and
|
||||
(
|
||||
this.getReceiver().asExpr().getExpr() instanceof Self and
|
||||
isPrivateKernelMethod(methodCall.getMethodName())
|
||||
or
|
||||
isPublicKernelMethod(methodCall.getMethodName())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
string getMethodName() { result = methodCall.getMethodName() }
|
||||
|
||||
int getNumberOfArguments() { result = methodCall.getNumberOfArguments() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +65,45 @@ private predicate isPrivateKernelMethod(string method) {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance methods on `BasicObject`, which are available to all classes.
|
||||
*/
|
||||
class BasicObjectInstanceMethodCall extends UnknownMethodCall {
|
||||
BasicObjectInstanceMethodCall() {
|
||||
this.getMethodName() in [
|
||||
"equal?", "instance_eval", "instance_exec", "method_missing", "singleton_method_added",
|
||||
"singleton_method_removed", "singleton_method_undefined"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance methods on `Object`, which are available to all classes except `BasicObject`.
|
||||
*/
|
||||
class ObjectInstanceMethodCall extends UnknownMethodCall {
|
||||
ObjectInstanceMethodCall() {
|
||||
this.getMethodName() in [
|
||||
"!~", "<=>", "===", "=~", "callable_methods", "define_singleton_method", "display",
|
||||
"do_until", "do_while", "dup", "enum_for", "eql?", "extend", "f", "freeze", "h", "hash",
|
||||
"inspect", "instance_of?", "instance_variable_defined?", "instance_variable_get",
|
||||
"instance_variable_set", "instance_variables", "is_a?", "itself", "kind_of?",
|
||||
"matching_methods", "method", "method_missing", "methods", "nil?", "object_id",
|
||||
"private_methods", "protected_methods", "public_method", "public_methods", "public_send",
|
||||
"remove_instance_variable", "respond_to?", "respond_to_missing?", "send",
|
||||
"shortest_abbreviation", "singleton_class", "singleton_method", "singleton_methods",
|
||||
"taint", "tainted?", "to_enum", "to_s", "trust", "untaint", "untrust", "untrusted?"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method calls which have no known target.
|
||||
* These will typically be calls to methods inherited from a superclass.
|
||||
*/
|
||||
class UnknownMethodCall extends MethodCall {
|
||||
UnknownMethodCall() { not exists(this.(Call).getATarget()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A system command executed via subshell literal syntax.
|
||||
* E.g.
|
||||
@@ -125,19 +171,14 @@ class SubshellHeredocExecution extends SystemCommandExecution::Range {
|
||||
* ```
|
||||
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-system
|
||||
*/
|
||||
class KernelSystemCall extends SystemCommandExecution::Range {
|
||||
KernelMethodCall methodCall;
|
||||
class KernelSystemCall extends SystemCommandExecution::Range, KernelMethodCall {
|
||||
KernelSystemCall() { this.getMethodName() = "system" }
|
||||
|
||||
KernelSystemCall() {
|
||||
methodCall.getMethodName() = "system" and
|
||||
this.asExpr().getExpr() = methodCall
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
// Kernel.system invokes a subshell if you provide a single string as argument
|
||||
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
|
||||
this.getNumberOfArguments() = 1 and arg = getAnArgument()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,19 +187,14 @@ class KernelSystemCall extends SystemCommandExecution::Range {
|
||||
* `Kernel.exec` takes the same argument forms as `Kernel.system`. See `KernelSystemCall` for details.
|
||||
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-exec
|
||||
*/
|
||||
class KernelExecCall extends SystemCommandExecution::Range {
|
||||
KernelMethodCall methodCall;
|
||||
class KernelExecCall extends SystemCommandExecution::Range, KernelMethodCall {
|
||||
KernelExecCall() { this.getMethodName() = "exec" }
|
||||
|
||||
KernelExecCall() {
|
||||
methodCall.getMethodName() = "exec" and
|
||||
this.asExpr().getExpr() = methodCall
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
// Kernel.exec invokes a subshell if you provide a single string as argument
|
||||
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
|
||||
this.getNumberOfArguments() = 1 and arg = getAnArgument()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,22 +205,17 @@ class KernelExecCall extends SystemCommandExecution::Range {
|
||||
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-spawn
|
||||
* TODO: document and handle the env and option arguments.
|
||||
* ```
|
||||
* spawn([env,] command... [,options]) → pid
|
||||
* spawn([env,] command... [,options]) -> pid
|
||||
* ```
|
||||
*/
|
||||
class KernelSpawnCall extends SystemCommandExecution::Range {
|
||||
KernelMethodCall methodCall;
|
||||
class KernelSpawnCall extends SystemCommandExecution::Range, KernelMethodCall {
|
||||
KernelSpawnCall() { this.getMethodName() = "spawn" }
|
||||
|
||||
KernelSpawnCall() {
|
||||
methodCall.getMethodName() = "spawn" and
|
||||
this.asExpr().getExpr() = methodCall
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
// Kernel.spawn invokes a subshell if you provide a single string as argument
|
||||
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
|
||||
this.getNumberOfArguments() = 1 and arg = getAnArgument()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,37 +272,62 @@ class Open3PipelineCall extends SystemCommandExecution::Range {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Kernel.eval`, which executes its argument as Ruby code.
|
||||
* A call to `Kernel.eval`, which executes its first argument as Ruby code.
|
||||
* ```ruby
|
||||
* a = 1
|
||||
* Kernel.eval("a = 2")
|
||||
* a # => 2
|
||||
* ```
|
||||
*/
|
||||
class EvalCallCodeExecution extends CodeExecution::Range {
|
||||
KernelMethodCall methodCall;
|
||||
class EvalCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
|
||||
EvalCallCodeExecution() { this.getMethodName() = "eval" }
|
||||
|
||||
EvalCallCodeExecution() {
|
||||
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "eval"
|
||||
}
|
||||
|
||||
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
|
||||
override DataFlow::Node getCode() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Kernel#send`, which executes its arguments as a Ruby method call.
|
||||
* A call to `Kernel#send`, which executes its first argument as a Ruby method call.
|
||||
* ```ruby
|
||||
* arr = []
|
||||
* arr.send("push", 1)
|
||||
* arr # => [1]
|
||||
* ```
|
||||
*/
|
||||
class SendCallCodeExecution extends CodeExecution::Range {
|
||||
KernelMethodCall methodCall;
|
||||
class SendCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
|
||||
SendCallCodeExecution() { this.getMethodName() = "send" }
|
||||
|
||||
SendCallCodeExecution() {
|
||||
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "send"
|
||||
override DataFlow::Node getCode() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `BasicObject#instance_eval`, which executes its first argument as Ruby code.
|
||||
*/
|
||||
class InstanceEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
|
||||
InstanceEvalCallCodeExecution() {
|
||||
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "instance_eval"
|
||||
}
|
||||
|
||||
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
|
||||
override DataFlow::Node getCode() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Module#class_eval`, which executes its first argument as Ruby code.
|
||||
*/
|
||||
class ClassEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
|
||||
ClassEvalCallCodeExecution() {
|
||||
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "class_eval"
|
||||
}
|
||||
|
||||
override DataFlow::Node getCode() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Module#module_eval`, which executes its first argument as Ruby code.
|
||||
*/
|
||||
class ModuleEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
|
||||
ModuleEvalCallCodeExecution() {
|
||||
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "module_eval"
|
||||
}
|
||||
|
||||
override DataFlow::Node getCode() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
182
ql/lib/codeql/ruby/frameworks/XmlParsing.qll
Normal file
182
ql/lib/codeql/ruby/frameworks/XmlParsing.qll
Normal file
@@ -0,0 +1,182 @@
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.controlflow.CfgNodes as CfgNodes
|
||||
|
||||
private class NokogiriXmlParserCall extends XmlParserCall::Range, DataFlow::CallNode {
|
||||
NokogiriXmlParserCall() {
|
||||
this =
|
||||
[
|
||||
API::getTopLevelMember("Nokogiri").getMember("XML"),
|
||||
API::getTopLevelMember("Nokogiri").getMember("XML").getMember("Document"),
|
||||
API::getTopLevelMember("Nokogiri")
|
||||
.getMember("XML")
|
||||
.getMember("SAX")
|
||||
.getMember("Parser")
|
||||
.getInstance()
|
||||
].getAMethodCall("parse")
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = this.getArgument(0) }
|
||||
|
||||
override predicate externalEntitiesEnabled() {
|
||||
this.getArgument(3) =
|
||||
[trackEnableFeature(TNOENT()), trackEnableFeature(TDTDLOAD()), trackDisableFeature(TNONET())]
|
||||
or
|
||||
// calls to methods that enable/disable features in a block argument passed to this parser call.
|
||||
// For example:
|
||||
// ```ruby
|
||||
// doc.parse(...) { |options| options.nononet; options.noent }
|
||||
// ```
|
||||
this.asExpr()
|
||||
.getExpr()
|
||||
.(MethodCall)
|
||||
.getBlock()
|
||||
.getAStmt()
|
||||
.getAChild*()
|
||||
.(MethodCall)
|
||||
.getMethodName() = ["noent", "dtdload", "nononet"]
|
||||
}
|
||||
}
|
||||
|
||||
private class LibXmlRubyXmlParserCall extends XmlParserCall::Range, DataFlow::CallNode {
|
||||
LibXmlRubyXmlParserCall() {
|
||||
this =
|
||||
[API::getTopLevelMember("LibXML").getMember("XML"), API::getTopLevelMember("XML")]
|
||||
.getMember(["Document", "Parser"])
|
||||
.getAMethodCall(["file", "io", "string"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = this.getArgument(0) }
|
||||
|
||||
override predicate externalEntitiesEnabled() {
|
||||
exists(Pair pair |
|
||||
pair = this.getArgument(1).asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
|
||||
pair.getKey().(Literal).getValueText() = "options" and
|
||||
pair.getValue() =
|
||||
[
|
||||
trackEnableFeature(TNOENT()), trackEnableFeature(TDTDLOAD()),
|
||||
trackDisableFeature(TNONET())
|
||||
].asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TFeature =
|
||||
TNOENT() or
|
||||
TNONET() or
|
||||
TDTDLOAD()
|
||||
|
||||
class Feature extends TFeature {
|
||||
abstract int getValue();
|
||||
|
||||
string toString() { result = getConstantName() }
|
||||
|
||||
abstract string getConstantName();
|
||||
}
|
||||
|
||||
private class FeatureNOENT extends Feature, TNOENT {
|
||||
override int getValue() { result = 2 }
|
||||
|
||||
override string getConstantName() { result = "NOENT" }
|
||||
}
|
||||
|
||||
private class FeatureNONET extends Feature, TNONET {
|
||||
override int getValue() { result = 2048 }
|
||||
|
||||
override string getConstantName() { result = "NONET" }
|
||||
}
|
||||
|
||||
private class FeatureDTDLOAD extends Feature, TDTDLOAD {
|
||||
override int getValue() { result = 4 }
|
||||
|
||||
override string getConstantName() { result = "DTDLOAD" }
|
||||
}
|
||||
|
||||
private API::Node parseOptionsModule() {
|
||||
result = API::getTopLevelMember("Nokogiri").getMember("XML").getMember("ParseOptions")
|
||||
or
|
||||
result =
|
||||
API::getTopLevelMember("LibXML").getMember("XML").getMember("Parser").getMember("Options")
|
||||
or
|
||||
result = API::getTopLevelMember("XML").getMember("Parser").getMember("Options")
|
||||
}
|
||||
|
||||
private predicate bitWiseAndOr(CfgNodes::ExprNodes::OperationCfgNode operation) {
|
||||
operation.getExpr() instanceof BitwiseAndExpr or
|
||||
operation.getExpr() instanceof AssignBitwiseAndExpr or
|
||||
operation.getExpr() instanceof BitwiseOrExpr or
|
||||
operation.getExpr() instanceof AssignBitwiseOrExpr
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackFeature(Feature f, boolean enable, TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
// An integer literal with the feature-bit enabled/disabled
|
||||
exists(int bitValue |
|
||||
bitValue = result.asExpr().getExpr().(IntegerLiteral).getValue().bitAnd(f.getValue())
|
||||
|
|
||||
if bitValue = 0 then enable = false else enable = true
|
||||
)
|
||||
or
|
||||
// Use of a constant f
|
||||
enable = true and
|
||||
result = parseOptionsModule().getMember(f.getConstantName()).getAUse()
|
||||
or
|
||||
// Treat `&`, `&=`, `|` and `|=` operators as if they preserve the on/off states
|
||||
// of their operands. This is an overapproximation but likely to work well in practice
|
||||
// because it makes little sense to explicitly set a feature to both `on` and `off` in the
|
||||
// same code.
|
||||
exists(CfgNodes::ExprNodes::OperationCfgNode operation |
|
||||
bitWiseAndOr(operation) and
|
||||
operation = result.asExpr().(CfgNodes::ExprNodes::OperationCfgNode) and
|
||||
operation.getAnOperand() = trackFeature(f, enable).asExpr()
|
||||
)
|
||||
or
|
||||
// The complement operator toggles a feature from enabled to disabled and vice-versa
|
||||
result.asExpr().getExpr() instanceof ComplementExpr and
|
||||
result.asExpr().(CfgNodes::ExprNodes::OperationCfgNode).getAnOperand() =
|
||||
trackFeature(f, enable.booleanNot()).asExpr()
|
||||
or
|
||||
// Nokogiri has a ParseOptions class that is a wrapper around the bit-fields and
|
||||
// provides methods for querying and updating the fields.
|
||||
result =
|
||||
API::getTopLevelMember("Nokogiri")
|
||||
.getMember("XML")
|
||||
.getMember("ParseOptions")
|
||||
.getAnInstantiation() and
|
||||
result.asExpr().(CfgNodes::ExprNodes::CallCfgNode).getArgument(0) =
|
||||
trackFeature(f, enable).asExpr()
|
||||
or
|
||||
// The Nokogiri ParseOptions class has methods for setting/unsetting features.
|
||||
// The method names are the lowercase variants of the constant names, with a "no"
|
||||
// prefix for unsetting a feature.
|
||||
exists(CfgNodes::ExprNodes::CallCfgNode call |
|
||||
enable = true and
|
||||
call.getExpr().(MethodCall).getMethodName() = f.getConstantName().toLowerCase()
|
||||
or
|
||||
enable = false and
|
||||
call.getExpr().(MethodCall).getMethodName() = "no" + f.getConstantName().toLowerCase()
|
||||
|
|
||||
(
|
||||
// these methods update the receiver
|
||||
result.flowsTo(any(DataFlow::Node n | n.asExpr() = call.getReceiver()))
|
||||
or
|
||||
// in addition they return the (updated) receiver to allow chaining calls.
|
||||
result.asExpr() = call
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackFeature(f, enable, t2).track(t2, t))
|
||||
}
|
||||
|
||||
private DataFlow::Node trackFeature(Feature f, boolean enable) {
|
||||
trackFeature(f, enable, TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
private DataFlow::Node trackEnableFeature(Feature f) { result = trackFeature(f, true) }
|
||||
|
||||
private DataFlow::Node trackDisableFeature(Feature f) { result = trackFeature(f, false) }
|
||||
@@ -17,11 +17,11 @@ private import codeql.ruby.ApiGraphs
|
||||
* TODO: pipelining, streaming responses
|
||||
* https://github.com/excon/excon/blob/master/README.md
|
||||
*/
|
||||
class ExconHTTPRequest extends HTTP::Client::Request::Range {
|
||||
class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node request;
|
||||
DataFlow::CallNode responseBody;
|
||||
|
||||
ExconHTTPRequest() {
|
||||
ExconHttpRequest() {
|
||||
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
|
||||
requestNode =
|
||||
[
|
||||
|
||||
@@ -13,11 +13,11 @@ private import codeql.ruby.ApiGraphs
|
||||
* connection.get("/").body
|
||||
* ```
|
||||
*/
|
||||
class FaradayHTTPRequest extends HTTP::Client::Request::Range {
|
||||
class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node request;
|
||||
DataFlow::CallNode responseBody;
|
||||
|
||||
FaradayHTTPRequest() {
|
||||
FaradayHttpRequest() {
|
||||
exists(API::Node requestNode |
|
||||
requestNode =
|
||||
[
|
||||
|
||||
40
ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll
Normal file
40
ql/lib/codeql/ruby/frameworks/http_clients/HttpClient.qll
Normal file
@@ -0,0 +1,40 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `HTTPClient`.
|
||||
* ```ruby
|
||||
* HTTPClient.get("http://example.com").body
|
||||
* HTTPClient.get_content("http://example.com")
|
||||
* ```
|
||||
*/
|
||||
class HttpClientRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node request;
|
||||
DataFlow::CallNode responseBody;
|
||||
|
||||
HttpClientRequest() {
|
||||
exists(API::Node requestNode, string method |
|
||||
request = requestNode.getAnImmediateUse() and
|
||||
method in [
|
||||
"get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
|
||||
]
|
||||
|
|
||||
requestNode = API::getTopLevelMember("HTTPClient").getReturn(method) and
|
||||
(
|
||||
// The `get_content` and `post_content` methods return the response body as a string.
|
||||
// The other methods return a `HTTPClient::Message` object which has various methods
|
||||
// that return the response body.
|
||||
method in ["get_content", "post_content"] and responseBody = request
|
||||
or
|
||||
not method in ["get_content", "put_content"] and
|
||||
responseBody = requestNode.getAMethodCall(["body", "http_body", "content", "dump"])
|
||||
) and
|
||||
this = request.asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
||||
|
||||
override string getFramework() { result = "HTTPClient" }
|
||||
}
|
||||
46
ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll
Normal file
46
ql/lib/codeql/ruby/frameworks/http_clients/Httparty.qll
Normal file
@@ -0,0 +1,46 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `HTTParty`.
|
||||
* ```ruby
|
||||
* # one-off request - returns the response body
|
||||
* HTTParty.get("http://example.com")
|
||||
*
|
||||
* # TODO: module inclusion
|
||||
* class MyClass
|
||||
* include HTTParty
|
||||
* end
|
||||
*
|
||||
* MyClass.new("http://example.com")
|
||||
* ```
|
||||
*/
|
||||
class HttpartyRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node request;
|
||||
DataFlow::CallNode responseBody;
|
||||
|
||||
HttpartyRequest() {
|
||||
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
|
||||
requestNode =
|
||||
API::getTopLevelMember("HTTParty")
|
||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||
(
|
||||
// If HTTParty can recognise the response type, it will parse and return it
|
||||
// directly from the request call. Otherwise, it will return a `HTTParty::Response`
|
||||
// object that has a `#body` method.
|
||||
// So if there's a call to `#body` on the response, treat that as the response body.
|
||||
exists(DataFlow::Node r | r = requestNode.getAMethodCall("body") | responseBody = r)
|
||||
or
|
||||
// Otherwise, treat the response as the response body.
|
||||
not exists(DataFlow::Node r | r = requestNode.getAMethodCall("body")) and
|
||||
responseBody = request
|
||||
) and
|
||||
this = request.asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
||||
|
||||
override string getFramework() { result = "HTTParty" }
|
||||
}
|
||||
@@ -13,11 +13,11 @@ private import codeql.ruby.dataflow.internal.DataFlowPublic
|
||||
* response = req.get("/")
|
||||
* ```
|
||||
*/
|
||||
class NetHTTPRequest extends HTTP::Client::Request::Range {
|
||||
class NetHttpRequest extends HTTP::Client::Request::Range {
|
||||
private DataFlow::CallNode request;
|
||||
private DataFlow::Node responseBody;
|
||||
|
||||
NetHTTPRequest() {
|
||||
NetHttpRequest() {
|
||||
exists(API::Node requestNode, string method |
|
||||
request = requestNode.getAnImmediateUse() and
|
||||
this = request.asExpr().getExpr()
|
||||
39
ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll
Normal file
39
ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll
Normal file
@@ -0,0 +1,39 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.StandardLibrary
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `OpenURI`.
|
||||
* ```ruby
|
||||
* Kernel.open("http://example.com").read
|
||||
* URI.open("http://example.com").readlines
|
||||
* URI.parse("http://example.com").open.read
|
||||
* ```
|
||||
*/
|
||||
class OpenURIRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node request;
|
||||
DataFlow::CallNode responseBody;
|
||||
|
||||
OpenURIRequest() {
|
||||
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
|
||||
requestNode =
|
||||
[API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
|
||||
.getReturn("open") and
|
||||
responseBody = requestNode.getAMethodCall(["read", "readlines"]) and
|
||||
this = request.asExpr().getExpr()
|
||||
)
|
||||
or
|
||||
// Kernel.open("http://example.com").read
|
||||
// open("http://example.com").read
|
||||
request instanceof KernelMethodCall and
|
||||
this.getMethodName() = "open" and
|
||||
request.asExpr().getExpr() = this and
|
||||
responseBody.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
|
||||
request.(DataFlow::LocalSourceNode).flowsTo(responseBody.getReceiver())
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
||||
|
||||
override string getFramework() { result = "OpenURI" }
|
||||
}
|
||||
@@ -8,11 +8,11 @@ private import codeql.ruby.ApiGraphs
|
||||
* RestClient.get("http://example.com").body
|
||||
* ```
|
||||
*/
|
||||
class RestClientHTTPRequest extends HTTP::Client::Request::Range {
|
||||
class RestClientHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node request;
|
||||
DataFlow::CallNode responseBody;
|
||||
|
||||
RestClientHTTPRequest() {
|
||||
RestClientHttpRequest() {
|
||||
exists(API::Node requestNode |
|
||||
requestNode =
|
||||
API::getTopLevelMember("RestClient")
|
||||
|
||||
28
ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll
Normal file
28
ql/lib/codeql/ruby/frameworks/http_clients/Typhoeus.qll
Normal file
@@ -0,0 +1,28 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* A call that makes an HTTP request using `Typhoeus`.
|
||||
* ```ruby
|
||||
* Typhoeus.get("http://example.com").body
|
||||
* ```
|
||||
*/
|
||||
class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node request;
|
||||
DataFlow::CallNode responseBody;
|
||||
|
||||
TyphoeusHttpRequest() {
|
||||
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
|
||||
requestNode =
|
||||
API::getTopLevelMember("Typhoeus")
|
||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||
responseBody = requestNode.getAMethodCall("body") and
|
||||
this = request.asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
||||
|
||||
override string getFramework() { result = "Typhoeus" }
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class PrintAstNode extends TPrintNode {
|
||||
* Holds if this node is at the specified location. The location spans column
|
||||
* `startcolumn` of line `startline` to column `endcolumn` of line `endline`
|
||||
* in file `filepath`. For more information, see
|
||||
* [LGTM locations](https://lgtm.com/help/ql/locations).
|
||||
* [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -11,7 +11,7 @@ class Node = DataFlowPublic::Node;
|
||||
|
||||
class TypeTrackingNode = DataFlowPublic::LocalSourceNode;
|
||||
|
||||
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2;
|
||||
predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
|
||||
|
||||
predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
|
||||
@@ -30,12 +30,21 @@ string getPossibleContentName() { result = getSetterCallAttributeName(_) }
|
||||
* recursion (or, at best, terrible performance), since identifying calls to library
|
||||
* methods is done using API graphs (which uses type tracking).
|
||||
*/
|
||||
predicate callStep(DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) {
|
||||
predicate callStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(ExprNodes::CallCfgNode call, CFG::CfgScope callable, int i |
|
||||
DataFlowDispatch::getTarget(call) = callable and
|
||||
nodeFrom.sourceArgumentOf(call, i) and
|
||||
nodeFrom.(DataFlowPrivate::ArgumentNode).sourceArgumentOf(call, i) and
|
||||
nodeTo.(DataFlowPrivate::ParameterNodeImpl).isSourceParameterOf(callable, i)
|
||||
)
|
||||
or
|
||||
// In normal data-flow, this will be a local flow step. But for type tracking
|
||||
// we model it as a call step, in order to avoid computing a potential
|
||||
// self-cross product of all calls to a function that returns one of its parameters
|
||||
// (only to later filter that flow out using `TypeTracker::append`).
|
||||
nodeTo =
|
||||
DataFlowPrivate::LocalFlow::getParameterDefNode(nodeFrom
|
||||
.(DataFlowPublic::ParameterNode)
|
||||
.getParameter())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,11 +54,18 @@ predicate callStep(DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPublic::Param
|
||||
* recursion (or, at best, terrible performance), since identifying calls to library
|
||||
* methods is done using API graphs (which uses type tracking).
|
||||
*/
|
||||
predicate returnStep(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) {
|
||||
predicate returnStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(ExprNodes::CallCfgNode call |
|
||||
nodeFrom instanceof DataFlowPrivate::ReturnNode and
|
||||
nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and
|
||||
nodeTo.asExpr().getNode() = call.getNode()
|
||||
)
|
||||
or
|
||||
// In normal data-flow, this will be a local flow step. But for type tracking
|
||||
// we model it as a returning flow step, in order to avoid computing a potential
|
||||
// self-cross product of all calls to a function that returns one of its parameters
|
||||
// (only to later filter that flow out using `TypeTracker::append`).
|
||||
nodeTo.(DataFlowPrivate::SynthReturnNode).getAnInput() = nodeFrom
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +121,6 @@ predicate basicStoreStep(Node nodeFrom, DataFlowPublic::LocalSourceNode nodeTo,
|
||||
*/
|
||||
private string getSetterCallAttributeName(AST::SetterMethodCall call) {
|
||||
// TODO: this should be exposed in `SetterMethodCall`
|
||||
not call instanceof AST::ElementReference and
|
||||
exists(string setterName |
|
||||
setterName = call.getMethodName() and result = setterName.prefix(setterName.length() - 1)
|
||||
)
|
||||
|
||||
@@ -1205,8 +1205,6 @@ ruby_yield_def(
|
||||
ruby_tokeninfo(
|
||||
unique int id: @ruby_token,
|
||||
int kind: int ref,
|
||||
int file: @file ref,
|
||||
int idx: int ref,
|
||||
string value: string ref,
|
||||
int loc: @location ref
|
||||
);
|
||||
@@ -1293,8 +1291,6 @@ erb_template_def(
|
||||
erb_tokeninfo(
|
||||
unique int id: @erb_token,
|
||||
int kind: int ref,
|
||||
int file: @file ref,
|
||||
int idx: int ref,
|
||||
string value: string ref,
|
||||
int loc: @location ref
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
description: Removed unused columns from the `*_tokeninfo` relations
|
||||
compatibility: full
|
||||
ruby_tokeninfo.rel: reorder ruby_tokeninfo.rel (int id, int kind, int file, int idx, string value, int loc) id kind value loc
|
||||
erb_tokeninfo.rel: reorder erb_tokeninfo.rel (int id, int kind, int file, int idx, string value, int loc) id kind value loc
|
||||
46
ql/src/queries/security/cwe-078/KernelOpen.qhelp
Normal file
46
ql/src/queries/security/cwe-078/KernelOpen.qhelp
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>If <code>Kernel.open</code> is given a file name that starts with a <code>|</code>
|
||||
character, it will execute the remaining string as a shell command. If a
|
||||
malicious user can control the file name, they can execute arbitrary code.
|
||||
The same vulnerability applies to <code>IO.read</code>.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Use <code>File.open</code> instead of <code>Kernel.open</code>, as the former
|
||||
does not have this vulnerability. Similarly, use <code>File.read</code> instead
|
||||
of <code>IO.read</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following example shows code that calls <code>Kernel.open</code> on a
|
||||
user-supplied file path.
|
||||
</p>
|
||||
|
||||
<sample src="examples/kernel_open.rb" />
|
||||
|
||||
<p>Instead, <code>File.open</code> should be used, as in the following example.</p>
|
||||
|
||||
<sample src="examples/file_open.rb" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Example CVE: <a href="https://www.ruby-lang.org/en/news/2021/05/02/os-command-injection-in-rdoc/">Command Injection in RDoc</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
76
ql/src/queries/security/cwe-078/KernelOpen.ql
Normal file
76
ql/src/queries/security/cwe-078/KernelOpen.ql
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @name Use of `Kernel.open` or `IO.read`
|
||||
* @description Using `Kernel.open` or `IO.read` may allow a malicious
|
||||
* user to execute arbitrary system commands.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.8
|
||||
* @precision high
|
||||
* @id rb/kernel-open
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-078
|
||||
* external/cwe/cwe-088
|
||||
* external/cwe/cwe-073
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.ApiGraphs
|
||||
import codeql.ruby.frameworks.StandardLibrary
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.dataflow.BarrierGuards
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* Method calls that have a suggested replacement.
|
||||
*/
|
||||
abstract class Replacement extends DataFlow::CallNode {
|
||||
abstract string getFrom();
|
||||
|
||||
abstract string getTo();
|
||||
}
|
||||
|
||||
class KernelOpenCall extends KernelMethodCall, Replacement {
|
||||
KernelOpenCall() { this.getMethodName() = "open" }
|
||||
|
||||
override string getFrom() { result = "Kernel.open" }
|
||||
|
||||
override string getTo() { result = "File.open" }
|
||||
}
|
||||
|
||||
class IOReadCall extends DataFlow::CallNode, Replacement {
|
||||
IOReadCall() { this = API::getTopLevelMember("IO").getAMethodCall("read") }
|
||||
|
||||
override string getFrom() { result = "IO.read" }
|
||||
|
||||
override string getTo() { result = "File.read" }
|
||||
}
|
||||
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "KernelOpen" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(KernelOpenCall c | c.getArgument(0) = sink)
|
||||
or
|
||||
exists(IOReadCall c | c.getArgument(0) = sink)
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof StringConstCompare or
|
||||
guard instanceof StringConstArrayInclusionCall
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
DataFlow::Node sourceNode, DataFlow::CallNode call
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
sourceNode = source.getNode() and
|
||||
call.asExpr().getExpr().(MethodCall).getArgument(0) = sink.getNode().asExpr().getExpr()
|
||||
select sink.getNode(), source, sink,
|
||||
"This call to " + call.(Replacement).getFrom() +
|
||||
" depends on a user-provided value. Replace it with " + call.(Replacement).getTo() + "."
|
||||
6
ql/src/queries/security/cwe-078/examples/file_open.rb
Normal file
6
ql/src/queries/security/cwe-078/examples/file_open.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class UsersController < ActionController::Base
|
||||
def create
|
||||
filename = params[:filename]
|
||||
File.open(filename)
|
||||
end
|
||||
end
|
||||
6
ql/src/queries/security/cwe-078/examples/kernel_open.rb
Normal file
6
ql/src/queries/security/cwe-078/examples/kernel_open.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class UsersController < ActionController::Base
|
||||
def create
|
||||
filename = params[:filename]
|
||||
open(filename) # BAD
|
||||
end
|
||||
end
|
||||
55
ql/src/queries/security/cwe-611/Xxe.qhelp
Normal file
55
ql/src/queries/security/cwe-611/Xxe.qhelp
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Parsing untrusted XML files with a weakly configured XML parser may lead to an
|
||||
XML External Entity (XXE) attack. This type of attack uses external entity references
|
||||
to access arbitrary files on a system, carry out denial-of-service (DoS) attacks, or server-side
|
||||
request forgery. Even when the result of parsing is not returned to the user, DoS attacks are still possible
|
||||
and out-of-band data retrieval techniques may allow attackers to steal sensitive data.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
The easiest way to prevent XXE attacks is to disable external entity handling when
|
||||
parsing untrusted data. How this is done depends on the library being used. Note that some
|
||||
libraries, such as <code>rexml</code>, <code>nokogiri</code> and <code>libxml-ruby</code>,
|
||||
disable entity expansion by default, so unless you have explicitly enabled entity expansion,
|
||||
no further action needs to be taken.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example uses the <code>nokogiri</code> XML parser to parse a string <code>xmlSrc</code>.
|
||||
If that string is from an untrusted source, this code may be vulnerable to an XXE attack, since
|
||||
the parser is invoked with the <code>noent</code> option set:
|
||||
</p>
|
||||
<sample src="examples/Xxe.rb"/>
|
||||
|
||||
<p>
|
||||
To guard against XXE attacks, the <code>noent</code> option should be omitted or cleared
|
||||
(e.g. using <code>nonoent</code>). This means that no entity expansion is undertaken at all,
|
||||
not even for standard internal entities such as <code>&amp;</code> or <code>&gt;</code>.
|
||||
If desired, these entities can be expanded in a separate step using utility functions.
|
||||
</p>
|
||||
<sample src="examples/XxeGood.rb"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML External Entity (XXE) Processing</a>.
|
||||
</li>
|
||||
<li>
|
||||
Timothy Morgen:
|
||||
<a href="https://research.nccgroup.com/2014/05/19/xml-schema-dtd-and-entity-attacks-a-compendium-of-known-techniques/">XML Schema, DTD, and Entity Attacks</a>.
|
||||
</li>
|
||||
<li>
|
||||
Timur Yunusov, Alexey Osipov:
|
||||
<a href="https://www.slideshare.net/qqlan/bh-ready-v4">XML Out-Of-Band Data Retrieval</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
43
ql/src/queries/security/cwe-611/Xxe.ql
Normal file
43
ql/src/queries/security/cwe-611/Xxe.ql
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @name XML external entity expansion
|
||||
* @description Parsing user input as an XML document with external
|
||||
* entity expansion is vulnerable to XXE attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.1
|
||||
* @precision high
|
||||
* @id rb/xxe
|
||||
* @tags security
|
||||
* external/cwe/cwe-611
|
||||
* external/cwe/cwe-776
|
||||
* external/cwe/cwe-827
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.Concepts
|
||||
import codeql.ruby.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class UnsafeXxeSink extends DataFlow::ExprNode {
|
||||
UnsafeXxeSink() {
|
||||
exists(XmlParserCall parse |
|
||||
parse.getInput() = this and
|
||||
parse.externalEntitiesEnabled()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class XxeConfig extends TaintTracking::Configuration {
|
||||
XxeConfig() { this = "XXE.ql::XxeConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeXxeSink }
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, XxeConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Unsafe parsing of XML file from $@.", source.getNode(),
|
||||
"user input"
|
||||
12
ql/src/queries/security/cwe-611/examples/Xxe.rb
Normal file
12
ql/src/queries/security/cwe-611/examples/Xxe.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
require "nokogiri"
|
||||
|
||||
def process_data1
|
||||
xmlSrc = request.body
|
||||
doc = Nokogiri::XML.parse(xmlSrc, nil, nil, Nokogiri::XML::ParseOptions::NOENT) # BAD
|
||||
end
|
||||
|
||||
def process_data2
|
||||
xmlSrc = request.body
|
||||
doc = Nokogiri::XML.parse(xmlSrc) { |config| config.noent } # BAD
|
||||
end
|
||||
|
||||
12
ql/src/queries/security/cwe-611/examples/XxeGood.rb
Normal file
12
ql/src/queries/security/cwe-611/examples/XxeGood.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
require "nokogiri"
|
||||
|
||||
def process_data1
|
||||
xmlSrc = request.body
|
||||
doc = Nokogiri::XML.parse(xmlSrc) # GOOD
|
||||
end
|
||||
|
||||
def process_data2
|
||||
xmlSrc = request.body
|
||||
doc = Nokogiri::XML.parse(xmlSrc) { |config| config.nonoent } # GOOD
|
||||
end
|
||||
|
||||
558
ql/test/library-tests/ast/ValueText.expected
Normal file
558
ql/test/library-tests/ast/ValueText.expected
Normal file
@@ -0,0 +1,558 @@
|
||||
| calls/calls.rb:11:1:11:3 | 123 | 123 |
|
||||
| calls/calls.rb:14:5:14:5 | 0 | 0 |
|
||||
| calls/calls.rb:14:8:14:8 | 1 | 1 |
|
||||
| calls/calls.rb:14:11:14:11 | 2 | 2 |
|
||||
| calls/calls.rb:17:15:17:15 | 1 | 1 |
|
||||
| calls/calls.rb:21:7:21:7 | 1 | 1 |
|
||||
| calls/calls.rb:25:1:25:3 | 123 | 123 |
|
||||
| calls/calls.rb:25:9:25:13 | "foo" | foo |
|
||||
| calls/calls.rb:26:7:26:7 | 1 | 1 |
|
||||
| calls/calls.rb:36:9:36:11 | 100 | 100 |
|
||||
| calls/calls.rb:36:14:36:16 | 200 | 200 |
|
||||
| calls/calls.rb:278:5:278:8 | :blah | blah |
|
||||
| calls/calls.rb:279:5:279:8 | :blah | blah |
|
||||
| calls/calls.rb:288:11:288:16 | "blah" | blah |
|
||||
| calls/calls.rb:289:11:289:11 | 1 | 1 |
|
||||
| calls/calls.rb:289:14:289:14 | 2 | 2 |
|
||||
| calls/calls.rb:289:17:289:17 | 3 | 3 |
|
||||
| calls/calls.rb:290:21:290:21 | 1 | 1 |
|
||||
| calls/calls.rb:291:22:291:22 | 2 | 2 |
|
||||
| calls/calls.rb:292:11:292:11 | 4 | 4 |
|
||||
| calls/calls.rb:292:14:292:14 | 5 | 5 |
|
||||
| calls/calls.rb:292:26:292:28 | 100 | 100 |
|
||||
| calls/calls.rb:293:11:293:11 | 6 | 6 |
|
||||
| calls/calls.rb:293:14:293:14 | 7 | 7 |
|
||||
| calls/calls.rb:293:27:293:29 | 200 | 200 |
|
||||
| calls/calls.rb:311:6:311:6 | 1 | 1 |
|
||||
| calls/calls.rb:314:1:314:8 | __synth__0 | 10 |
|
||||
| calls/calls.rb:314:12:314:13 | 10 | 10 |
|
||||
| calls/calls.rb:315:1:315:6 | __synth__0 | 10 |
|
||||
| calls/calls.rb:315:5:315:5 | 0 | 0 |
|
||||
| calls/calls.rb:315:10:315:11 | 10 | 10 |
|
||||
| calls/calls.rb:316:1:316:8 | 0 | 0 |
|
||||
| calls/calls.rb:316:12:316:19 | 1 | 1 |
|
||||
| calls/calls.rb:316:12:316:19 | -2 | -2 |
|
||||
| calls/calls.rb:316:22:316:27 | -1 | -1 |
|
||||
| calls/calls.rb:316:26:316:26 | 4 | 4 |
|
||||
| calls/calls.rb:316:32:316:32 | 1 | 1 |
|
||||
| calls/calls.rb:316:35:316:35 | 2 | 2 |
|
||||
| calls/calls.rb:316:38:316:38 | 3 | 3 |
|
||||
| calls/calls.rb:316:41:316:41 | 4 | 4 |
|
||||
| calls/calls.rb:317:1:317:1 | 0 | 0 |
|
||||
| calls/calls.rb:317:5:317:10 | 1 | 1 |
|
||||
| calls/calls.rb:317:5:317:10 | -1 | -1 |
|
||||
| calls/calls.rb:317:9:317:9 | 5 | 5 |
|
||||
| calls/calls.rb:317:15:317:15 | 1 | 1 |
|
||||
| calls/calls.rb:317:18:317:18 | 2 | 2 |
|
||||
| calls/calls.rb:317:21:317:21 | 3 | 3 |
|
||||
| calls/calls.rb:318:15:318:15 | 1 | 1 |
|
||||
| calls/calls.rb:319:5:319:5 | 0 | 0 |
|
||||
| calls/calls.rb:319:5:319:5 | __synth__1 | 0 |
|
||||
| calls/calls.rb:319:5:319:5 | __synth__1 | 0 |
|
||||
| calls/calls.rb:319:11:319:11 | 1 | 1 |
|
||||
| calls/calls.rb:320:9:320:9 | 0 | 0 |
|
||||
| calls/calls.rb:320:9:320:9 | __synth__1 | 0 |
|
||||
| calls/calls.rb:320:9:320:9 | __synth__1 | 0 |
|
||||
| calls/calls.rb:320:31:320:31 | 1 | 1 |
|
||||
| calls/calls.rb:320:37:320:37 | 2 | 2 |
|
||||
| constants/constants.rb:3:19:3:27 | "const_a" | const_a |
|
||||
| constants/constants.rb:6:15:6:23 | "const_b" | const_b |
|
||||
| constants/constants.rb:17:12:17:18 | "Hello" | Hello |
|
||||
| constants/constants.rb:17:22:17:45 | CONST_A | const_a |
|
||||
| constants/constants.rb:17:49:17:64 | CONST_B | const_b |
|
||||
| constants/constants.rb:20:14:20:19 | "Vera" | Vera |
|
||||
| constants/constants.rb:20:22:20:28 | "Chuck" | Chuck |
|
||||
| constants/constants.rb:20:31:20:36 | "Dave" | Dave |
|
||||
| constants/constants.rb:28:11:28:15 | "foo" | foo |
|
||||
| constants/constants.rb:37:30:37:33 | 1024 | 1024 |
|
||||
| constants/constants.rb:39:6:39:31 | MAX_SIZE | 1024 |
|
||||
| control/cases.rb:2:5:2:5 | 0 | 0 |
|
||||
| control/cases.rb:3:5:3:5 | 0 | 0 |
|
||||
| control/cases.rb:4:5:4:5 | 0 | 0 |
|
||||
| control/cases.rb:5:5:5:5 | 0 | 0 |
|
||||
| control/cases.rb:8:6:8:6 | a | 0 |
|
||||
| control/cases.rb:9:6:9:6 | b | 0 |
|
||||
| control/cases.rb:10:5:10:7 | 100 | 100 |
|
||||
| control/cases.rb:11:6:11:6 | c | 0 |
|
||||
| control/cases.rb:11:9:11:9 | d | 0 |
|
||||
| control/cases.rb:12:5:12:7 | 200 | 200 |
|
||||
| control/cases.rb:14:5:14:7 | 300 | 300 |
|
||||
| control/cases.rb:19:6:19:6 | a | 0 |
|
||||
| control/cases.rb:19:10:19:10 | b | 0 |
|
||||
| control/cases.rb:19:18:19:19 | 10 | 10 |
|
||||
| control/cases.rb:20:6:20:6 | a | 0 |
|
||||
| control/cases.rb:20:11:20:11 | b | 0 |
|
||||
| control/cases.rb:20:18:20:19 | 20 | 20 |
|
||||
| control/cases.rb:21:6:21:6 | a | 0 |
|
||||
| control/cases.rb:21:10:21:10 | b | 0 |
|
||||
| control/cases.rb:21:18:21:19 | 30 | 30 |
|
||||
| control/conditionals.rb:2:5:2:5 | 0 | 0 |
|
||||
| control/conditionals.rb:3:5:3:5 | 0 | 0 |
|
||||
| control/conditionals.rb:4:5:4:5 | 0 | 0 |
|
||||
| control/conditionals.rb:5:5:5:5 | 0 | 0 |
|
||||
| control/conditionals.rb:6:5:6:5 | 0 | 0 |
|
||||
| control/conditionals.rb:7:5:7:5 | 0 | 0 |
|
||||
| control/conditionals.rb:10:4:10:4 | a | 0 |
|
||||
| control/conditionals.rb:10:8:10:8 | b | 0 |
|
||||
| control/conditionals.rb:11:5:11:5 | c | 0 |
|
||||
| control/conditionals.rb:15:4:15:4 | a | 0 |
|
||||
| control/conditionals.rb:15:9:15:9 | b | 0 |
|
||||
| control/conditionals.rb:16:5:16:5 | c | 0 |
|
||||
| control/conditionals.rb:18:5:18:5 | d | 0 |
|
||||
| control/conditionals.rb:22:4:22:4 | a | 0 |
|
||||
| control/conditionals.rb:22:9:22:9 | 0 | 0 |
|
||||
| control/conditionals.rb:23:5:23:5 | c | 0 |
|
||||
| control/conditionals.rb:24:7:24:7 | a | 0 |
|
||||
| control/conditionals.rb:24:12:24:12 | 1 | 1 |
|
||||
| control/conditionals.rb:25:5:25:5 | d | 0 |
|
||||
| control/conditionals.rb:26:7:26:7 | a | 0 |
|
||||
| control/conditionals.rb:26:12:26:12 | 2 | 2 |
|
||||
| control/conditionals.rb:27:5:27:5 | e | 0 |
|
||||
| control/conditionals.rb:29:5:29:5 | f | 0 |
|
||||
| control/conditionals.rb:33:4:33:4 | a | 0 |
|
||||
| control/conditionals.rb:33:9:33:9 | 0 | 0 |
|
||||
| control/conditionals.rb:34:5:34:5 | b | 0 |
|
||||
| control/conditionals.rb:35:7:35:7 | a | 0 |
|
||||
| control/conditionals.rb:35:12:35:12 | 1 | 1 |
|
||||
| control/conditionals.rb:36:5:36:5 | c | 0 |
|
||||
| control/conditionals.rb:40:8:40:8 | a | 0 |
|
||||
| control/conditionals.rb:40:12:40:12 | b | 0 |
|
||||
| control/conditionals.rb:41:5:41:5 | c | 0 |
|
||||
| control/conditionals.rb:45:8:45:8 | a | 0 |
|
||||
| control/conditionals.rb:45:13:45:13 | b | 0 |
|
||||
| control/conditionals.rb:46:5:46:5 | c | 0 |
|
||||
| control/conditionals.rb:48:5:48:5 | d | 0 |
|
||||
| control/conditionals.rb:52:5:52:5 | b | 0 |
|
||||
| control/conditionals.rb:52:10:52:10 | c | 0 |
|
||||
| control/conditionals.rb:52:14:52:14 | d | 0 |
|
||||
| control/conditionals.rb:55:5:55:5 | b | 0 |
|
||||
| control/conditionals.rb:55:14:55:14 | c | 0 |
|
||||
| control/conditionals.rb:55:18:55:18 | d | 0 |
|
||||
| control/conditionals.rb:58:5:58:5 | b | 0 |
|
||||
| control/conditionals.rb:58:9:58:9 | c | 0 |
|
||||
| control/conditionals.rb:58:13:58:13 | d | 0 |
|
||||
| control/conditionals.rb:58:13:58:17 | ... + ... | 1 |
|
||||
| control/conditionals.rb:58:17:58:17 | 1 | 1 |
|
||||
| control/conditionals.rb:58:21:58:21 | e | 0 |
|
||||
| control/conditionals.rb:58:21:58:25 | ... - ... | -2 |
|
||||
| control/conditionals.rb:58:25:58:25 | 2 | 2 |
|
||||
| control/conditionals.rb:61:8:61:8 | b | 0 |
|
||||
| control/conditionals.rb:62:5:62:5 | c | 0 |
|
||||
| control/conditionals.rb:67:8:67:8 | b | 0 |
|
||||
| control/conditionals.rb:69:5:69:5 | c | 0 |
|
||||
| control/loops.rb:2:7:2:7 | 0 | 0 |
|
||||
| control/loops.rb:3:7:3:7 | 0 | 0 |
|
||||
| control/loops.rb:4:5:4:5 | 0 | 0 |
|
||||
| control/loops.rb:5:5:5:5 | 0 | 0 |
|
||||
| control/loops.rb:6:5:6:5 | 0 | 0 |
|
||||
| control/loops.rb:9:10:9:10 | 1 | 1 |
|
||||
| control/loops.rb:9:13:9:14 | 10 | 10 |
|
||||
| control/loops.rb:16:10:16:10 | 1 | 1 |
|
||||
| control/loops.rb:16:13:16:14 | 10 | 10 |
|
||||
| control/loops.rb:22:20:22:22 | :foo | foo |
|
||||
| control/loops.rb:22:25:22:25 | 0 | 0 |
|
||||
| control/loops.rb:22:28:22:30 | :bar | bar |
|
||||
| control/loops.rb:22:33:22:33 | 1 | 1 |
|
||||
| control/loops.rb:28:22:28:24 | :foo | foo |
|
||||
| control/loops.rb:28:27:28:27 | 0 | 0 |
|
||||
| control/loops.rb:28:30:28:32 | :bar | bar |
|
||||
| control/loops.rb:28:35:28:35 | 1 | 1 |
|
||||
| control/loops.rb:35:11:35:11 | y | 0 |
|
||||
| control/loops.rb:36:8:36:8 | 1 | 1 |
|
||||
| control/loops.rb:37:8:37:8 | 1 | 1 |
|
||||
| control/loops.rb:42:11:42:11 | y | 0 |
|
||||
| control/loops.rb:43:8:43:8 | 1 | 1 |
|
||||
| control/loops.rb:44:8:44:8 | 2 | 2 |
|
||||
| control/loops.rb:48:6:48:6 | 1 | 1 |
|
||||
| control/loops.rb:48:14:48:14 | y | 0 |
|
||||
| control/loops.rb:51:12:51:12 | y | 0 |
|
||||
| control/loops.rb:52:8:52:8 | 1 | 1 |
|
||||
| control/loops.rb:53:8:53:8 | 1 | 1 |
|
||||
| control/loops.rb:57:11:57:11 | y | 0 |
|
||||
| control/loops.rb:58:8:58:8 | 1 | 1 |
|
||||
| control/loops.rb:59:8:59:8 | 4 | 4 |
|
||||
| control/loops.rb:63:6:63:6 | 1 | 1 |
|
||||
| control/loops.rb:63:19:63:19 | 0 | 0 |
|
||||
| control/loops.rb:66:11:66:11 | y | 0 |
|
||||
| erb/template.html.erb:19:5:19:17 | "hello world" | hello world |
|
||||
| erb/template.html.erb:25:9:25:10 | "" | |
|
||||
| erb/template.html.erb:27:16:27:20 | "foo" | foo |
|
||||
| erb/template.html.erb:27:23:27:27 | "bar" | bar |
|
||||
| erb/template.html.erb:27:30:27:34 | "baz" | baz |
|
||||
| gems/Gemfile:1:8:1:29 | "https://rubygems.org" | https://rubygems.org |
|
||||
| gems/Gemfile:3:5:3:13 | "foo_gem" | foo_gem |
|
||||
| gems/Gemfile:3:16:3:23 | "~> 2.0" | ~> 2.0 |
|
||||
| gems/Gemfile:5:8:5:33 | "https://gems.example.com" | https://gems.example.com |
|
||||
| gems/Gemfile:6:7:6:14 | "my_gem" | my_gem |
|
||||
| gems/Gemfile:6:17:6:21 | "1.0" | 1.0 |
|
||||
| gems/Gemfile:7:7:7:19 | "another_gem" | another_gem |
|
||||
| gems/Gemfile:7:22:7:28 | "3.1.4" | 3.1.4 |
|
||||
| gems/lib/test.rb:3:10:3:16 | "Hello" | Hello |
|
||||
| gems/test.gemspec:2:3:2:8 | __synth__0 | test |
|
||||
| gems/test.gemspec:2:19:2:24 | "test" | test |
|
||||
| gems/test.gemspec:3:3:3:11 | __synth__0 | 0.0.0 |
|
||||
| gems/test.gemspec:3:19:3:25 | "0.0.0" | 0.0.0 |
|
||||
| gems/test.gemspec:4:3:4:11 | __synth__0 | foo! |
|
||||
| gems/test.gemspec:4:19:4:24 | "foo!" | foo! |
|
||||
| gems/test.gemspec:5:3:5:15 | __synth__0 | A test |
|
||||
| gems/test.gemspec:5:19:5:26 | "A test" | A test |
|
||||
| gems/test.gemspec:6:20:6:30 | "Mona Lisa" | Mona Lisa |
|
||||
| gems/test.gemspec:7:3:7:9 | __synth__0 | mona@example.com |
|
||||
| gems/test.gemspec:7:19:7:36 | "mona@example.com" | mona@example.com |
|
||||
| gems/test.gemspec:8:20:8:32 | "lib/test.rb" | lib/test.rb |
|
||||
| gems/test.gemspec:9:3:9:12 | __synth__0 | https://github.com/github/codeql-ruby |
|
||||
| gems/test.gemspec:9:19:9:57 | "https://github.com/github/cod..." | https://github.com/github/codeql-ruby |
|
||||
| literals/literals.rb:2:1:2:3 | nil | nil |
|
||||
| literals/literals.rb:3:1:3:3 | NIL | NIL |
|
||||
| literals/literals.rb:4:1:4:5 | false | false |
|
||||
| literals/literals.rb:5:1:5:5 | FALSE | FALSE |
|
||||
| literals/literals.rb:6:1:6:4 | true | true |
|
||||
| literals/literals.rb:7:1:7:4 | TRUE | TRUE |
|
||||
| literals/literals.rb:10:1:10:4 | 1234 | 1234 |
|
||||
| literals/literals.rb:11:1:11:5 | 5_678 | 5_678 |
|
||||
| literals/literals.rb:12:1:12:1 | 0 | 0 |
|
||||
| literals/literals.rb:13:1:13:5 | 0d900 | 0d900 |
|
||||
| literals/literals.rb:16:1:16:6 | 0x1234 | 0x1234 |
|
||||
| literals/literals.rb:17:1:17:10 | 0xdeadbeef | 0xdeadbeef |
|
||||
| literals/literals.rb:18:1:18:11 | 0xF00D_face | 0xF00D_face |
|
||||
| literals/literals.rb:21:1:21:4 | 0123 | 0123 |
|
||||
| literals/literals.rb:22:1:22:5 | 0o234 | 0o234 |
|
||||
| literals/literals.rb:23:1:23:6 | 0O45_6 | 0O45_6 |
|
||||
| literals/literals.rb:26:1:26:10 | 0b10010100 | 0b10010100 |
|
||||
| literals/literals.rb:27:1:27:11 | 0B011_01101 | 0B011_01101 |
|
||||
| literals/literals.rb:30:1:30:5 | 12.34 | 12.34 |
|
||||
| literals/literals.rb:31:1:31:7 | 1234e-2 | 1234e-2 |
|
||||
| literals/literals.rb:32:1:32:7 | 1.234E1 | 1.234E1 |
|
||||
| literals/literals.rb:35:1:35:3 | 23r | 23r |
|
||||
| literals/literals.rb:36:1:36:5 | 9.85r | 9.85r |
|
||||
| literals/literals.rb:39:1:39:2 | 2i | 2i |
|
||||
| literals/literals.rb:46:1:46:2 | "" | |
|
||||
| literals/literals.rb:47:1:47:2 | "" | |
|
||||
| literals/literals.rb:48:1:48:7 | "hello" | hello |
|
||||
| literals/literals.rb:49:1:49:9 | "goodbye" | goodbye |
|
||||
| literals/literals.rb:50:1:50:30 | "string with escaped \\" quote" | string with escaped \\" quote |
|
||||
| literals/literals.rb:51:1:51:21 | "string with " quote" | string with " quote |
|
||||
| literals/literals.rb:52:1:52:14 | "foo bar baz" | foo bar baz |
|
||||
| literals/literals.rb:53:1:53:15 | "foo bar baz" | foo bar baz |
|
||||
| literals/literals.rb:54:1:54:20 | "foo ' bar " baz'" | foo ' bar " baz' |
|
||||
| literals/literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | FOO ' BAR " BAZ' |
|
||||
| literals/literals.rb:56:1:56:12 | "foo\\ bar" | foo\\ bar |
|
||||
| literals/literals.rb:57:1:57:12 | "foo\\ bar" | foo\\ bar |
|
||||
| literals/literals.rb:58:13:58:13 | 2 | 2 |
|
||||
| literals/literals.rb:58:13:58:17 | ... + ... | 4 |
|
||||
| literals/literals.rb:58:17:58:17 | 2 | 2 |
|
||||
| literals/literals.rb:59:15:59:15 | 3 | 3 |
|
||||
| literals/literals.rb:59:15:59:19 | ... + ... | 7 |
|
||||
| literals/literals.rb:59:19:59:19 | 4 | 4 |
|
||||
| literals/literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | 2 + 2 = #{ 2 + 2 } |
|
||||
| literals/literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | 3 + 4 = #{ 3 + 4 } |
|
||||
| literals/literals.rb:62:1:62:5 | "foo" | foo |
|
||||
| literals/literals.rb:62:7:62:11 | "bar" | bar |
|
||||
| literals/literals.rb:62:13:62:17 | "baz" | baz |
|
||||
| literals/literals.rb:63:1:63:7 | "foo" | foo |
|
||||
| literals/literals.rb:63:9:63:13 | "bar" | bar |
|
||||
| literals/literals.rb:63:15:63:19 | "baz" | baz |
|
||||
| literals/literals.rb:64:1:64:5 | "foo" | foo |
|
||||
| literals/literals.rb:64:14:64:14 | 1 | 1 |
|
||||
| literals/literals.rb:64:14:64:18 | ... * ... | 1 |
|
||||
| literals/literals.rb:64:18:64:18 | 1 | 1 |
|
||||
| literals/literals.rb:64:23:64:27 | "baz" | baz |
|
||||
| literals/literals.rb:65:17:65:17 | 2 | 2 |
|
||||
| literals/literals.rb:65:17:65:21 | ... + ... | 5 |
|
||||
| literals/literals.rb:65:21:65:21 | 3 | 3 |
|
||||
| literals/literals.rb:66:17:66:17 | 1 | 1 |
|
||||
| literals/literals.rb:66:17:66:19 | ... + ... | 10 |
|
||||
| literals/literals.rb:66:19:66:19 | 9 | 9 |
|
||||
| literals/literals.rb:69:1:69:2 | ?x | ?x |
|
||||
| literals/literals.rb:70:1:70:3 | ?\\n | ?\\n |
|
||||
| literals/literals.rb:71:1:71:3 | ?\\s | ?\\s |
|
||||
| literals/literals.rb:72:1:72:3 | ?\\\\ | ?\\\\ |
|
||||
| literals/literals.rb:73:1:73:7 | ?\\u{58} | ?\\u{58} |
|
||||
| literals/literals.rb:74:1:74:5 | ?\\C-a | ?\\C-a |
|
||||
| literals/literals.rb:75:1:75:5 | ?\\M-a | ?\\M-a |
|
||||
| literals/literals.rb:76:1:76:8 | ?\\M-\\C-a | ?\\M-\\C-a |
|
||||
| literals/literals.rb:77:1:77:8 | ?\\C-\\M-a | ?\\C-\\M-a |
|
||||
| literals/literals.rb:80:1:80:3 | :"" | |
|
||||
| literals/literals.rb:81:1:81:6 | :hello | hello |
|
||||
| literals/literals.rb:82:1:82:10 | :"foo bar" | foo bar |
|
||||
| literals/literals.rb:83:1:83:10 | :"bar baz" | bar baz |
|
||||
| literals/literals.rb:84:3:84:5 | :foo | foo |
|
||||
| literals/literals.rb:84:8:84:12 | "bar" | bar |
|
||||
| literals/literals.rb:85:1:85:10 | :"wibble" | wibble |
|
||||
| literals/literals.rb:86:1:86:17 | :"wibble wobble" | wibble wobble |
|
||||
| literals/literals.rb:87:10:87:10 | 2 | 2 |
|
||||
| literals/literals.rb:87:10:87:14 | ... + ... | 4 |
|
||||
| literals/literals.rb:87:14:87:14 | 2 | 2 |
|
||||
| literals/literals.rb:88:1:88:17 | :"foo_#{ 1 + 1 }" | foo_#{ 1 + 1 } |
|
||||
| literals/literals.rb:89:1:89:18 | :"foo_#{ 3 - 2 }" | foo_#{ 3 - 2 } |
|
||||
| literals/literals.rb:93:2:93:2 | 1 | 1 |
|
||||
| literals/literals.rb:93:5:93:5 | 2 | 2 |
|
||||
| literals/literals.rb:93:8:93:8 | 3 | 3 |
|
||||
| literals/literals.rb:94:2:94:2 | 4 | 4 |
|
||||
| literals/literals.rb:94:5:94:5 | 5 | 5 |
|
||||
| literals/literals.rb:94:8:94:9 | 12 | 12 |
|
||||
| literals/literals.rb:94:8:94:13 | ... / ... | 6 |
|
||||
| literals/literals.rb:94:13:94:13 | 2 | 2 |
|
||||
| literals/literals.rb:95:2:95:2 | 7 | 7 |
|
||||
| literals/literals.rb:95:6:95:6 | 8 | 8 |
|
||||
| literals/literals.rb:95:9:95:9 | 9 | 9 |
|
||||
| literals/literals.rb:99:4:99:6 | "foo" | foo |
|
||||
| literals/literals.rb:99:8:99:10 | "bar" | bar |
|
||||
| literals/literals.rb:99:12:99:14 | "baz" | baz |
|
||||
| literals/literals.rb:100:4:100:6 | "foo" | foo |
|
||||
| literals/literals.rb:100:8:100:10 | "bar" | bar |
|
||||
| literals/literals.rb:100:12:100:14 | "baz" | baz |
|
||||
| literals/literals.rb:101:4:101:6 | "foo" | foo |
|
||||
| literals/literals.rb:101:13:101:13 | 1 | 1 |
|
||||
| literals/literals.rb:101:13:101:15 | ... + ... | 2 |
|
||||
| literals/literals.rb:101:15:101:15 | 1 | 1 |
|
||||
| literals/literals.rb:101:18:101:20 | "baz" | baz |
|
||||
| literals/literals.rb:102:4:102:6 | "foo" | foo |
|
||||
| literals/literals.rb:102:8:102:16 | "bar#{1+1}" | bar#{1+1} |
|
||||
| literals/literals.rb:102:18:102:20 | "baz" | baz |
|
||||
| literals/literals.rb:106:4:106:6 | :"foo" | foo |
|
||||
| literals/literals.rb:106:8:106:10 | :"bar" | bar |
|
||||
| literals/literals.rb:106:12:106:14 | :"baz" | baz |
|
||||
| literals/literals.rb:107:4:107:6 | :"foo" | foo |
|
||||
| literals/literals.rb:107:8:107:10 | :"bar" | bar |
|
||||
| literals/literals.rb:107:12:107:14 | :"baz" | baz |
|
||||
| literals/literals.rb:108:4:108:6 | :"foo" | foo |
|
||||
| literals/literals.rb:108:14:108:14 | 2 | 2 |
|
||||
| literals/literals.rb:108:14:108:18 | ... + ... | 6 |
|
||||
| literals/literals.rb:108:18:108:18 | 4 | 4 |
|
||||
| literals/literals.rb:108:22:108:24 | :"baz" | baz |
|
||||
| literals/literals.rb:109:4:109:6 | :"foo" | foo |
|
||||
| literals/literals.rb:109:8:109:12 | :"bar#{" | bar#{ |
|
||||
| literals/literals.rb:109:14:109:14 | :"2" | 2 |
|
||||
| literals/literals.rb:109:16:109:16 | :"+" | + |
|
||||
| literals/literals.rb:109:18:109:18 | :"4" | 4 |
|
||||
| literals/literals.rb:109:20:109:20 | :"}" | } |
|
||||
| literals/literals.rb:109:22:109:24 | :"baz" | baz |
|
||||
| literals/literals.rb:113:3:113:5 | :foo | foo |
|
||||
| literals/literals.rb:113:8:113:8 | 1 | 1 |
|
||||
| literals/literals.rb:113:11:113:14 | :bar | bar |
|
||||
| literals/literals.rb:113:19:113:19 | 2 | 2 |
|
||||
| literals/literals.rb:113:22:113:26 | "baz" | baz |
|
||||
| literals/literals.rb:113:31:113:31 | 3 | 3 |
|
||||
| literals/literals.rb:114:3:114:5 | :foo | foo |
|
||||
| literals/literals.rb:114:8:114:8 | 7 | 7 |
|
||||
| literals/literals.rb:117:2:117:2 | 1 | 1 |
|
||||
| literals/literals.rb:117:5:117:6 | 10 | 10 |
|
||||
| literals/literals.rb:118:2:118:2 | 1 | 1 |
|
||||
| literals/literals.rb:118:6:118:7 | 10 | 10 |
|
||||
| literals/literals.rb:119:2:119:2 | 1 | 1 |
|
||||
| literals/literals.rb:119:7:119:7 | 0 | 0 |
|
||||
| literals/literals.rb:120:9:120:9 | 2 | 2 |
|
||||
| literals/literals.rb:120:9:120:11 | ... + ... | 5 |
|
||||
| literals/literals.rb:120:11:120:11 | 3 | 3 |
|
||||
| literals/literals.rb:121:2:121:2 | 1 | 1 |
|
||||
| literals/literals.rb:122:4:122:4 | 1 | 1 |
|
||||
| literals/literals.rb:123:2:123:2 | 0 | 0 |
|
||||
| literals/literals.rb:123:6:123:6 | 1 | 1 |
|
||||
| literals/literals.rb:126:1:126:7 | `ls -l` | ls -l |
|
||||
| literals/literals.rb:127:1:127:9 | `ls -l` | ls -l |
|
||||
| literals/literals.rb:128:11:128:11 | 1 | 1 |
|
||||
| literals/literals.rb:128:11:128:15 | ... + ... | 2 |
|
||||
| literals/literals.rb:128:15:128:15 | 1 | 1 |
|
||||
| literals/literals.rb:129:13:129:13 | 5 | 5 |
|
||||
| literals/literals.rb:129:13:129:17 | ... - ... | 1 |
|
||||
| literals/literals.rb:129:17:129:17 | 4 | 4 |
|
||||
| literals/literals.rb:132:1:132:2 | // | |
|
||||
| literals/literals.rb:133:1:133:5 | /foo/ | foo |
|
||||
| literals/literals.rb:134:1:134:6 | /foo/ | foo |
|
||||
| literals/literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | foo+\\sbar\\S |
|
||||
| literals/literals.rb:136:8:136:8 | 1 | 1 |
|
||||
| literals/literals.rb:136:8:136:12 | ... + ... | 2 |
|
||||
| literals/literals.rb:136:12:136:12 | 1 | 1 |
|
||||
| literals/literals.rb:137:1:137:8 | /foo/ | foo |
|
||||
| literals/literals.rb:138:1:138:4 | // | |
|
||||
| literals/literals.rb:139:1:139:7 | /foo/ | foo |
|
||||
| literals/literals.rb:140:1:140:8 | /foo/ | foo |
|
||||
| literals/literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | foo+\\sbar\\S |
|
||||
| literals/literals.rb:142:10:142:10 | 1 | 1 |
|
||||
| literals/literals.rb:142:10:142:14 | ... + ... | 2 |
|
||||
| literals/literals.rb:142:14:142:14 | 1 | 1 |
|
||||
| literals/literals.rb:143:1:143:10 | /foo/ | foo |
|
||||
| literals/literals.rb:146:1:146:34 | "abcdefghijklmnopqrstuvwxyzabcdef" | abcdefghijklmnopqrstuvwxyzabcdef |
|
||||
| literals/literals.rb:147:1:147:35 | "foobarfoobarfoobarfoobarfooba..." | foobarfoobarfoobarfoobarfoobarfoo |
|
||||
| literals/literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | foobar\\\\foobar\\\\foobar\\\\foobar\\\\foobar |
|
||||
| literals/literals.rb:151:9:151:13 | <<SQL | \nselect * from table\n |
|
||||
| literals/literals.rb:158:11:158:16 | <<-BLA | \nsome text\\nand some more\n |
|
||||
| literals/literals.rb:163:9:163:19 | <<~SQUIGGLY | \n indented stuff\n |
|
||||
| literals/literals.rb:176:10:176:19 | <<`SCRIPT` | \n cat file.txt\n |
|
||||
| misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js |
|
||||
| misc/misc.rb:1:7:1:11 | "bar" | bar |
|
||||
| misc/misc.rb:3:7:3:9 | foo | foo |
|
||||
| misc/misc.rb:3:12:3:15 | :foo | foo |
|
||||
| misc/misc.rb:3:18:3:21 | foo= | foo= |
|
||||
| misc/misc.rb:3:24:3:25 | [] | [] |
|
||||
| misc/misc.rb:3:28:3:30 | []= | []= |
|
||||
| misc/misc.rb:4:15:4:17 | bar | bar |
|
||||
| misc/misc.rb:5:7:5:9 | nil | nil |
|
||||
| misc/misc.rb:5:12:5:15 | true | true |
|
||||
| misc/misc.rb:5:18:5:22 | false | false |
|
||||
| misc/misc.rb:5:25:5:29 | super | super |
|
||||
| misc/misc.rb:5:32:5:35 | self | self |
|
||||
| misc/misc.rb:7:7:7:9 | new | new |
|
||||
| misc/misc.rb:7:11:7:14 | :old | old |
|
||||
| misc/misc.rb:8:7:8:10 | foo= | foo= |
|
||||
| misc/misc.rb:8:12:8:14 | []= | []= |
|
||||
| misc/misc.rb:9:7:9:11 | super | super |
|
||||
| misc/misc.rb:9:13:9:16 | self | self |
|
||||
| misc/misc.rb:10:13:10:15 | bar | bar |
|
||||
| misc/misc.rb:10:19:10:24 | :"foo" | foo |
|
||||
| modules/classes.rb:11:28:11:31 | :baz | baz |
|
||||
| modules/classes.rb:22:10:22:12 | "a" | a |
|
||||
| modules/classes.rb:26:10:26:12 | "b" | b |
|
||||
| modules/classes.rb:30:17:30:19 | 123 | 123 |
|
||||
| modules/classes.rb:40:5:40:11 | "hello" | hello |
|
||||
| modules/classes.rb:41:10:41:10 | x | hello |
|
||||
| modules/classes.rb:43:5:43:7 | 100 | 100 |
|
||||
| modules/classes.rb:47:10:47:17 | "wibble" | wibble |
|
||||
| modules/classes.rb:51:18:51:20 | 456 | 456 |
|
||||
| modules/modules.rb:12:10:12:26 | "module Foo::Bar" | module Foo::Bar |
|
||||
| modules/modules.rb:13:19:13:19 | 0 | 0 |
|
||||
| modules/modules.rb:22:8:22:19 | "module Foo" | module Foo |
|
||||
| modules/modules.rb:23:17:23:17 | 1 | 1 |
|
||||
| modules/modules.rb:33:8:33:25 | "module Foo again" | module Foo again |
|
||||
| modules/modules.rb:34:17:34:17 | 2 | 2 |
|
||||
| modules/modules.rb:44:8:44:19 | "module Bar" | module Bar |
|
||||
| modules/modules.rb:45:17:45:17 | 3 | 3 |
|
||||
| modules/modules.rb:55:8:55:30 | "module Foo::Bar again" | module Foo::Bar again |
|
||||
| modules/modules.rb:56:17:56:17 | 4 | 4 |
|
||||
| modules/toplevel.rb:1:6:1:12 | "world" | world |
|
||||
| modules/toplevel.rb:3:12:3:16 | "!!!" | !!! |
|
||||
| modules/toplevel.rb:5:14:5:20 | "hello" | hello |
|
||||
| operations/operations.rb:3:5:3:5 | 0 | 0 |
|
||||
| operations/operations.rb:4:5:4:5 | 0 | 0 |
|
||||
| operations/operations.rb:5:7:5:7 | 0 | 0 |
|
||||
| operations/operations.rb:6:8:6:8 | 0 | 0 |
|
||||
| operations/operations.rb:7:7:7:7 | 0 | 0 |
|
||||
| operations/operations.rb:8:7:8:7 | 0 | 0 |
|
||||
| operations/operations.rb:9:10:9:10 | 0 | 0 |
|
||||
| operations/operations.rb:10:5:10:5 | 0 | 0 |
|
||||
| operations/operations.rb:11:8:11:8 | 0 | 0 |
|
||||
| operations/operations.rb:12:5:12:5 | 0 | 0 |
|
||||
| operations/operations.rb:13:8:13:8 | 0 | 0 |
|
||||
| operations/operations.rb:14:7:14:7 | 0 | 0 |
|
||||
| operations/operations.rb:15:9:15:9 | 0 | 0 |
|
||||
| operations/operations.rb:16:7:16:7 | 0 | 0 |
|
||||
| operations/operations.rb:17:5:17:5 | 0 | 0 |
|
||||
| operations/operations.rb:18:5:18:5 | 0 | 0 |
|
||||
| operations/operations.rb:19:5:19:5 | 0 | 0 |
|
||||
| operations/operations.rb:20:5:20:5 | 0 | 0 |
|
||||
| operations/operations.rb:23:2:23:2 | a | 0 |
|
||||
| operations/operations.rb:24:5:24:5 | b | 0 |
|
||||
| operations/operations.rb:25:2:25:3 | 14 | 14 |
|
||||
| operations/operations.rb:26:2:26:2 | 7 | 7 |
|
||||
| operations/operations.rb:27:2:27:2 | x | 0 |
|
||||
| operations/operations.rb:28:10:28:12 | foo | 0 |
|
||||
| operations/operations.rb:29:17:29:17 | 1 | 1 |
|
||||
| operations/operations.rb:29:22:29:22 | 2 | 2 |
|
||||
| operations/operations.rb:29:26:29:26 | :a | a |
|
||||
| operations/operations.rb:29:28:29:28 | 3 | 3 |
|
||||
| operations/operations.rb:29:34:29:34 | :b | b |
|
||||
| operations/operations.rb:29:36:29:36 | 4 | 4 |
|
||||
| operations/operations.rb:29:39:29:39 | :c | c |
|
||||
| operations/operations.rb:29:41:29:41 | 5 | 5 |
|
||||
| operations/operations.rb:32:1:32:1 | w | 0 |
|
||||
| operations/operations.rb:32:1:32:7 | ... + ... | 234 |
|
||||
| operations/operations.rb:32:5:32:7 | 234 | 234 |
|
||||
| operations/operations.rb:33:1:33:1 | x | 0 |
|
||||
| operations/operations.rb:33:1:33:6 | ... - ... | -17 |
|
||||
| operations/operations.rb:33:5:33:6 | 17 | 17 |
|
||||
| operations/operations.rb:34:1:34:1 | y | 0 |
|
||||
| operations/operations.rb:34:1:34:6 | ... * ... | 0 |
|
||||
| operations/operations.rb:34:5:34:6 | 10 | 10 |
|
||||
| operations/operations.rb:35:1:35:1 | z | 0 |
|
||||
| operations/operations.rb:35:1:35:5 | ... / ... | 0 |
|
||||
| operations/operations.rb:35:5:35:5 | 2 | 2 |
|
||||
| operations/operations.rb:36:1:36:3 | num | 0 |
|
||||
| operations/operations.rb:36:7:36:7 | 2 | 2 |
|
||||
| operations/operations.rb:37:1:37:4 | base | 0 |
|
||||
| operations/operations.rb:37:9:37:13 | power | 0 |
|
||||
| operations/operations.rb:40:1:40:3 | foo | 0 |
|
||||
| operations/operations.rb:40:8:40:10 | bar | 0 |
|
||||
| operations/operations.rb:41:1:41:3 | baz | 0 |
|
||||
| operations/operations.rb:41:9:41:11 | qux | 0 |
|
||||
| operations/operations.rb:42:1:42:1 | a | 0 |
|
||||
| operations/operations.rb:42:6:42:6 | b | 0 |
|
||||
| operations/operations.rb:43:1:43:1 | x | 0 |
|
||||
| operations/operations.rb:43:6:43:6 | y | 0 |
|
||||
| operations/operations.rb:46:1:46:1 | x | 0 |
|
||||
| operations/operations.rb:46:6:46:6 | 3 | 3 |
|
||||
| operations/operations.rb:47:1:47:1 | y | 0 |
|
||||
| operations/operations.rb:47:6:47:7 | 16 | 16 |
|
||||
| operations/operations.rb:48:1:48:3 | foo | 0 |
|
||||
| operations/operations.rb:48:7:48:10 | 0xff | 0xff |
|
||||
| operations/operations.rb:49:1:49:3 | bar | 0 |
|
||||
| operations/operations.rb:49:7:49:10 | 0x02 | 0x02 |
|
||||
| operations/operations.rb:50:1:50:3 | baz | 0 |
|
||||
| operations/operations.rb:50:7:50:9 | qux | 0 |
|
||||
| operations/operations.rb:53:1:53:1 | x | 0 |
|
||||
| operations/operations.rb:53:6:53:6 | y | 0 |
|
||||
| operations/operations.rb:54:1:54:1 | a | 0 |
|
||||
| operations/operations.rb:54:6:54:8 | 123 | 123 |
|
||||
| operations/operations.rb:55:1:55:1 | m | 0 |
|
||||
| operations/operations.rb:55:7:55:7 | n | 0 |
|
||||
| operations/operations.rb:58:1:58:1 | x | 0 |
|
||||
| operations/operations.rb:58:5:58:5 | 0 | 0 |
|
||||
| operations/operations.rb:59:1:59:1 | y | 0 |
|
||||
| operations/operations.rb:59:6:59:8 | 100 | 100 |
|
||||
| operations/operations.rb:60:1:60:1 | a | 0 |
|
||||
| operations/operations.rb:60:5:60:5 | b | 0 |
|
||||
| operations/operations.rb:61:1:61:1 | 7 | 7 |
|
||||
| operations/operations.rb:61:6:61:8 | foo | 0 |
|
||||
| operations/operations.rb:64:1:64:1 | a | 0 |
|
||||
| operations/operations.rb:64:7:64:7 | b | 0 |
|
||||
| operations/operations.rb:65:1:65:4 | name | 0 |
|
||||
| operations/operations.rb:65:9:65:15 | /foo.*/ | foo.* |
|
||||
| operations/operations.rb:66:1:66:6 | handle | 0 |
|
||||
| operations/operations.rb:66:11:66:17 | /.*bar/ | .*bar |
|
||||
| operations/operations.rb:69:1:69:1 | x | 0 |
|
||||
| operations/operations.rb:69:3:69:4 | ... + ... | 128 |
|
||||
| operations/operations.rb:69:6:69:8 | 128 | 128 |
|
||||
| operations/operations.rb:70:1:70:1 | y | 0 |
|
||||
| operations/operations.rb:70:3:70:4 | ... - ... | -32 |
|
||||
| operations/operations.rb:70:6:70:7 | 32 | 32 |
|
||||
| operations/operations.rb:71:1:71:1 | a | 0 |
|
||||
| operations/operations.rb:71:3:71:4 | ... * ... | 0 |
|
||||
| operations/operations.rb:71:6:71:7 | 12 | 12 |
|
||||
| operations/operations.rb:72:1:72:1 | b | 0 |
|
||||
| operations/operations.rb:72:3:72:4 | ... / ... | 0 |
|
||||
| operations/operations.rb:72:6:72:6 | 4 | 4 |
|
||||
| operations/operations.rb:73:1:73:1 | z | 0 |
|
||||
| operations/operations.rb:73:6:73:6 | 2 | 2 |
|
||||
| operations/operations.rb:74:1:74:3 | foo | 0 |
|
||||
| operations/operations.rb:74:9:74:11 | bar | 0 |
|
||||
| operations/operations.rb:77:2:77:2 | x | 128 |
|
||||
| operations/operations.rb:77:8:77:8 | y | -32 |
|
||||
| operations/operations.rb:78:2:78:2 | a | 0 |
|
||||
| operations/operations.rb:78:8:78:8 | b | 0 |
|
||||
| operations/operations.rb:81:8:81:8 | 2 | 2 |
|
||||
| operations/operations.rb:82:2:82:2 | y | -32 |
|
||||
| operations/operations.rb:82:8:82:8 | 3 | 3 |
|
||||
| operations/operations.rb:83:9:83:12 | mask | 0 |
|
||||
| operations/operations.rb:84:2:84:4 | bar | 0 |
|
||||
| operations/operations.rb:84:9:84:12 | 0x01 | 0x01 |
|
||||
| operations/operations.rb:85:2:85:4 | baz | 0 |
|
||||
| operations/operations.rb:85:9:85:11 | qux | 0 |
|
||||
| operations/operations.rb:88:8:88:8 | 1 | 1 |
|
||||
| operations/operations.rb:89:9:89:9 | 2 | 2 |
|
||||
| operations/operations.rb:91:9:91:9 | 3 | 3 |
|
||||
| operations/operations.rb:92:10:92:10 | 4 | 4 |
|
||||
| operations/operations.rb:95:15:95:15 | 5 | 5 |
|
||||
| operations/operations.rb:96:16:96:16 | 6 | 6 |
|
||||
| params/params.rb:41:46:41:46 | 7 | 7 |
|
||||
| params/params.rb:47:19:47:21 | :bar | bar |
|
||||
| params/params.rb:47:24:47:24 | 2 | 2 |
|
||||
| params/params.rb:47:27:47:29 | :foo | foo |
|
||||
| params/params.rb:47:32:47:32 | 3 | 3 |
|
||||
| params/params.rb:49:37:49:39 | 100 | 100 |
|
||||
| params/params.rb:53:44:53:44 | 3 | 3 |
|
||||
| params/params.rb:58:46:58:46 | 0 | 0 |
|
||||
| params/params.rb:58:56:58:58 | 100 | 100 |
|
||||
| params/params.rb:63:14:63:19 | "Zeus" | Zeus |
|
||||
| params/params.rb:65:41:65:42 | 99 | 99 |
|
||||
| params/params.rb:70:42:70:45 | 1000 | 1000 |
|
||||
| params/params.rb:70:52:70:53 | 20 | 20 |
|
||||
4
ql/test/library-tests/ast/ValueText.ql
Normal file
4
ql/test/library-tests/ast/ValueText.ql
Normal file
@@ -0,0 +1,4 @@
|
||||
import ruby
|
||||
|
||||
from Expr e
|
||||
select e, e.getValueText()
|
||||
@@ -41,8 +41,8 @@ break_ensure.rb:
|
||||
#-----| -> do ...
|
||||
|
||||
# 3| ... > ...
|
||||
#-----| true -> break
|
||||
#-----| raise -> for ... in ...
|
||||
#-----| true -> break
|
||||
#-----| false -> if ...
|
||||
|
||||
# 3| element
|
||||
@@ -580,12 +580,12 @@ cfg.html.erb:
|
||||
# 12| self
|
||||
#-----| -> call to a
|
||||
|
||||
# 12| :id
|
||||
#-----| -> "a"
|
||||
|
||||
# 12| Pair
|
||||
#-----| -> call to link_to
|
||||
|
||||
# 12| :id
|
||||
#-----| -> "a"
|
||||
|
||||
# 12| "a"
|
||||
#-----| -> Pair
|
||||
|
||||
@@ -1493,12 +1493,12 @@ cfg.rb:
|
||||
# 97| "d"
|
||||
#-----| -> Pair
|
||||
|
||||
# 97| :e
|
||||
#-----| -> "f"
|
||||
|
||||
# 97| Pair
|
||||
#-----| -> {...}
|
||||
|
||||
# 97| :e
|
||||
#-----| -> "f"
|
||||
|
||||
# 97| "f"
|
||||
#-----| -> Pair
|
||||
|
||||
@@ -1826,6 +1826,9 @@ cfg.rb:
|
||||
# 134| EmptyModule
|
||||
#-----| -> ... rescue ...
|
||||
|
||||
# 136| ... rescue ...
|
||||
#-----| -> 1
|
||||
|
||||
# 136| ... / ...
|
||||
#-----| raise -> self
|
||||
#-----| -> __synth__0
|
||||
@@ -1833,9 +1836,6 @@ cfg.rb:
|
||||
# 136| 1
|
||||
#-----| -> 0
|
||||
|
||||
# 136| ... rescue ...
|
||||
#-----| -> 1
|
||||
|
||||
# 136| 0
|
||||
#-----| -> ... / ...
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ruby
|
||||
import codeql.ruby.dataflow.internal.DataFlowPrivate
|
||||
|
||||
select any(ReturnNode node)
|
||||
select any(ReturningNode node)
|
||||
|
||||
@@ -13,8 +13,8 @@ actionControllerActionMethods
|
||||
| app/controllers/foo/bars_controller.rb:15:3:19:5 | show |
|
||||
paramsCalls
|
||||
| ActiveRecordInjection.rb:35:30:35:35 | call to params |
|
||||
| ActiveRecordInjection.rb:39:30:39:35 | call to params |
|
||||
| ActiveRecordInjection.rb:43:32:43:37 | call to params |
|
||||
| ActiveRecordInjection.rb:39:29:39:34 | call to params |
|
||||
| ActiveRecordInjection.rb:43:31:43:36 | call to params |
|
||||
| ActiveRecordInjection.rb:48:21:48:26 | call to params |
|
||||
| ActiveRecordInjection.rb:54:34:54:39 | call to params |
|
||||
| ActiveRecordInjection.rb:56:23:56:28 | call to params |
|
||||
@@ -24,7 +24,7 @@ paramsCalls
|
||||
| ActiveRecordInjection.rb:77:12:77:17 | call to params |
|
||||
| ActiveRecordInjection.rb:83:12:83:17 | call to params |
|
||||
| ActiveRecordInjection.rb:88:15:88:20 | call to params |
|
||||
| ActiveRecordInjection.rb:94:22:94:27 | call to params |
|
||||
| ActiveRecordInjection.rb:94:21:94:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:8:21:8:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:9:10:9:15 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:16:21:16:26 | call to params |
|
||||
@@ -32,8 +32,8 @@ paramsCalls
|
||||
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
|
||||
paramsSources
|
||||
| ActiveRecordInjection.rb:35:30:35:35 | call to params |
|
||||
| ActiveRecordInjection.rb:39:30:39:35 | call to params |
|
||||
| ActiveRecordInjection.rb:43:32:43:37 | call to params |
|
||||
| ActiveRecordInjection.rb:39:29:39:34 | call to params |
|
||||
| ActiveRecordInjection.rb:43:31:43:36 | call to params |
|
||||
| ActiveRecordInjection.rb:48:21:48:26 | call to params |
|
||||
| ActiveRecordInjection.rb:54:34:54:39 | call to params |
|
||||
| ActiveRecordInjection.rb:56:23:56:28 | call to params |
|
||||
@@ -43,7 +43,7 @@ paramsSources
|
||||
| ActiveRecordInjection.rb:77:12:77:17 | call to params |
|
||||
| ActiveRecordInjection.rb:83:12:83:17 | call to params |
|
||||
| ActiveRecordInjection.rb:88:15:88:20 | call to params |
|
||||
| ActiveRecordInjection.rb:94:22:94:27 | call to params |
|
||||
| ActiveRecordInjection.rb:94:21:94:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:8:21:8:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:9:10:9:15 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:16:21:16:26 | call to params |
|
||||
|
||||
@@ -4,13 +4,13 @@ activeRecordModelClasses
|
||||
| ActiveRecordInjection.rb:19:1:25:3 | Admin |
|
||||
activeRecordSqlExecutionRanges
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" |
|
||||
| ActiveRecordInjection.rb:23:17:23:25 | condition |
|
||||
| ActiveRecordInjection.rb:23:16:23:24 | condition |
|
||||
| ActiveRecordInjection.rb:35:30:35:44 | ...[...] |
|
||||
| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:39:20:39:42 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:43:22:43:44 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL |
|
||||
| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:68:21:68:33 | ... + ... |
|
||||
| ActiveRecordInjection.rb:68:20:68:32 | ... + ... |
|
||||
| ActiveRecordInjection.rb:75:16:75:28 | "name #{...}" |
|
||||
| ActiveRecordInjection.rb:80:20:80:39 | "username = #{...}" |
|
||||
activeRecordModelClassMethodCalls
|
||||
@@ -19,28 +19,28 @@ activeRecordModelClassMethodCalls
|
||||
| ActiveRecordInjection.rb:10:5:10:68 | call to find |
|
||||
| ActiveRecordInjection.rb:15:5:15:40 | call to find_by |
|
||||
| ActiveRecordInjection.rb:15:5:15:46 | call to users |
|
||||
| ActiveRecordInjection.rb:23:5:23:26 | call to destroy_all |
|
||||
| ActiveRecordInjection.rb:23:5:23:25 | call to destroy_by |
|
||||
| ActiveRecordInjection.rb:35:5:35:45 | call to calculate |
|
||||
| ActiveRecordInjection.rb:39:5:39:44 | call to delete_all |
|
||||
| ActiveRecordInjection.rb:43:5:43:47 | call to destroy_all |
|
||||
| ActiveRecordInjection.rb:39:5:39:43 | call to delete_by |
|
||||
| ActiveRecordInjection.rb:43:5:43:46 | call to destroy_by |
|
||||
| ActiveRecordInjection.rb:47:5:47:35 | call to where |
|
||||
| ActiveRecordInjection.rb:54:5:54:14 | call to where |
|
||||
| ActiveRecordInjection.rb:54:5:54:48 | call to not |
|
||||
| ActiveRecordInjection.rb:56:5:56:51 | call to authenticate |
|
||||
| ActiveRecordInjection.rb:68:5:68:34 | call to delete_all |
|
||||
| ActiveRecordInjection.rb:68:5:68:33 | call to delete_by |
|
||||
| ActiveRecordInjection.rb:75:5:75:29 | call to order |
|
||||
| ActiveRecordInjection.rb:80:7:80:40 | call to find_by |
|
||||
| ActiveRecordInjection.rb:85:5:85:33 | call to find_by |
|
||||
| ActiveRecordInjection.rb:88:5:88:34 | call to find |
|
||||
| ActiveRecordInjection.rb:94:5:94:46 | call to delete_all |
|
||||
| ActiveRecordInjection.rb:94:5:94:45 | call to delete_by |
|
||||
potentiallyUnsafeSqlExecutingMethodCall
|
||||
| ActiveRecordInjection.rb:10:5:10:68 | call to find |
|
||||
| ActiveRecordInjection.rb:23:5:23:26 | call to destroy_all |
|
||||
| ActiveRecordInjection.rb:23:5:23:25 | call to destroy_by |
|
||||
| ActiveRecordInjection.rb:35:5:35:45 | call to calculate |
|
||||
| ActiveRecordInjection.rb:39:5:39:44 | call to delete_all |
|
||||
| ActiveRecordInjection.rb:43:5:43:47 | call to destroy_all |
|
||||
| ActiveRecordInjection.rb:39:5:39:43 | call to delete_by |
|
||||
| ActiveRecordInjection.rb:43:5:43:46 | call to destroy_by |
|
||||
| ActiveRecordInjection.rb:47:5:47:35 | call to where |
|
||||
| ActiveRecordInjection.rb:54:5:54:48 | call to not |
|
||||
| ActiveRecordInjection.rb:68:5:68:34 | call to delete_all |
|
||||
| ActiveRecordInjection.rb:68:5:68:33 | call to delete_by |
|
||||
| ActiveRecordInjection.rb:75:5:75:29 | call to order |
|
||||
| ActiveRecordInjection.rb:80:7:80:40 | call to find_by |
|
||||
|
||||
@@ -17,10 +17,10 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
class Admin < User
|
||||
def self.delete_all(condition = nil)
|
||||
# BAD: `delete_all` overrides an ActiveRecord method, but doesn't perform
|
||||
def self.delete_by(condition = nil)
|
||||
# BAD: `delete_by` overrides an ActiveRecord method, but doesn't perform
|
||||
# any validation before passing its arguments on to another ActiveRecord method
|
||||
destroy_all(condition)
|
||||
destroy_by(condition)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,11 +36,11 @@ class FooController < ActionController::Base
|
||||
|
||||
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
|
||||
# where `params[:id]` is unsanitized
|
||||
User.delete_all("id = '#{params[:id]}'")
|
||||
User.delete_by("id = '#{params[:id]}'")
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
|
||||
# where `params[:id]` is unsanitized
|
||||
User.destroy_all(["id = '#{params[:id]}'"])
|
||||
User.destroy_by(["id = '#{params[:id]}'"])
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE id BETWEEN '#{params[:min_id]}' AND 100000`
|
||||
# where `params[:min_id]` is unsanitized
|
||||
@@ -65,7 +65,7 @@ class BarController < ApplicationController
|
||||
|
||||
# BAD: executes `DELETE FROM "users" WHERE (id = #{uid})`
|
||||
# where `uid` is unsantized
|
||||
User.delete_all("id " + uidEq)
|
||||
User.delete_by("id " + uidEq)
|
||||
end
|
||||
|
||||
def safe_paths
|
||||
@@ -91,6 +91,6 @@ end
|
||||
|
||||
class BazController < BarController
|
||||
def yet_another_handler
|
||||
Admin.delete_all(params[:admin_condition])
|
||||
Admin.delete_by(params[:admin_condition])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Uses of eval and send
|
||||
|
||||
eval("raise \"error\"")
|
||||
eval("raise \"error\"", binding, "file", 1)
|
||||
send("raise", "error")
|
||||
|
||||
a = []
|
||||
a.send("raise", "error")
|
||||
a.send("push", "1")
|
||||
|
||||
class Foo
|
||||
def eval(x)
|
||||
@@ -21,3 +21,6 @@ class Foo
|
||||
end
|
||||
|
||||
Foo.new.send("exit", 1)
|
||||
Foo.new.instance_eval("self.class", "file.rb", 3)
|
||||
Foo.class_eval("def foo; 1; end", "file.rb", 1)
|
||||
Foo.module_eval("def bar; 1; end", "other_file.rb", 2)
|
||||
@@ -59,7 +59,13 @@ open3PipelineCallExecutions
|
||||
| CommandExecution.rb:64:1:64:44 | call to pipeline_start |
|
||||
| CommandExecution.rb:65:1:65:38 | call to pipeline |
|
||||
evalCallCodeExecutions
|
||||
| Eval.rb:3:1:3:23 | call to eval |
|
||||
| Eval.rb:3:1:3:43 | call to eval | Eval.rb:3:6:3:22 | "raise \\"error\\"" |
|
||||
sendCallCodeExecutions
|
||||
| Eval.rb:4:1:4:22 | call to send |
|
||||
| Eval.rb:7:1:7:24 | call to send |
|
||||
| Eval.rb:4:1:4:22 | call to send | Eval.rb:4:6:4:12 | "raise" |
|
||||
| Eval.rb:7:1:7:19 | call to send | Eval.rb:7:8:7:13 | "push" |
|
||||
instanceEvalCallCodeExecutions
|
||||
| Eval.rb:24:1:24:49 | call to instance_eval | Eval.rb:24:23:24:34 | "self.class" |
|
||||
classEvalCallCodeExecutions
|
||||
| Eval.rb:25:1:25:47 | call to class_eval | Eval.rb:25:16:25:32 | "def foo; 1; end" |
|
||||
moduleEvalCallCodeExecutions
|
||||
| Eval.rb:26:1:26:54 | call to module_eval | Eval.rb:26:17:26:33 | "def bar; 1; end" |
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import codeql.ruby.frameworks.StandardLibrary
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query predicate subshellLiteralExecutions(SubshellLiteralExecution e) { any() }
|
||||
|
||||
@@ -14,6 +15,18 @@ query predicate open3CallExecutions(Open3Call c) { any() }
|
||||
|
||||
query predicate open3PipelineCallExecutions(Open3PipelineCall c) { any() }
|
||||
|
||||
query predicate evalCallCodeExecutions(EvalCallCodeExecution e) { any() }
|
||||
query DataFlow::Node evalCallCodeExecutions(EvalCallCodeExecution e) { result = e.getCode() }
|
||||
|
||||
query predicate sendCallCodeExecutions(SendCallCodeExecution e) { any() }
|
||||
query DataFlow::Node sendCallCodeExecutions(SendCallCodeExecution e) { result = e.getCode() }
|
||||
|
||||
query DataFlow::Node instanceEvalCallCodeExecutions(InstanceEvalCallCodeExecution e) {
|
||||
result = e.getCode()
|
||||
}
|
||||
|
||||
query DataFlow::Node classEvalCallCodeExecutions(ClassEvalCallCodeExecution e) {
|
||||
result = e.getCode()
|
||||
}
|
||||
|
||||
query DataFlow::Node moduleEvalCallCodeExecutions(ModuleEvalCallCodeExecution e) {
|
||||
result = e.getCode()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import codeql.ruby.frameworks.http_clients.Excon
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node exconHTTPRequests(ExconHTTPRequest e) { result = e.getResponseBody() }
|
||||
query DataFlow::Node exconHttpRequests(ExconHttpRequest e) { result = e.getResponseBody() }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import codeql.ruby.frameworks.http_clients.Faraday
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node faradayHTTPRequests(FaradayHTTPRequest e) { result = e.getResponseBody() }
|
||||
query DataFlow::Node faradayHttpRequests(FaradayHttpRequest e) { result = e.getResponseBody() }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
| HttpClient.rb:3:9:3:45 | call to get | HttpClient.rb:4:1:4:10 | call to body |
|
||||
| HttpClient.rb:6:9:6:65 | call to post | HttpClient.rb:7:1:7:13 | call to content |
|
||||
| HttpClient.rb:9:9:9:64 | call to put | HttpClient.rb:10:1:10:15 | call to http_body |
|
||||
| HttpClient.rb:12:9:12:48 | call to delete | HttpClient.rb:13:1:13:10 | call to dump |
|
||||
| HttpClient.rb:15:9:15:46 | call to head | HttpClient.rb:16:1:16:10 | call to body |
|
||||
| HttpClient.rb:18:9:18:49 | call to options | HttpClient.rb:19:1:19:13 | call to content |
|
||||
| HttpClient.rb:21:9:21:47 | call to trace | HttpClient.rb:22:1:22:15 | call to http_body |
|
||||
| HttpClient.rb:24:9:24:53 | call to get_content | HttpClient.rb:24:9:24:53 | call to get_content |
|
||||
| HttpClient.rb:26:10:26:74 | call to post_content | HttpClient.rb:26:10:26:74 | call to post_content |
|
||||
@@ -0,0 +1,4 @@
|
||||
import codeql.ruby.frameworks.http_clients.HttpClient
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node httpClientRequests(HttpClientRequest e) { result = e.getResponseBody() }
|
||||
26
ql/test/library-tests/frameworks/http_clients/HttpClient.rb
Normal file
26
ql/test/library-tests/frameworks/http_clients/HttpClient.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
require "httpclient"
|
||||
|
||||
resp1 = HTTPClient.get("http://example.com/")
|
||||
resp1.body
|
||||
|
||||
resp2 = HTTPClient.post("http://example.com/", body: "some_data")
|
||||
resp2.content
|
||||
|
||||
resp3 = HTTPClient.put("http://example.com/", body: "some_data")
|
||||
resp3.http_body
|
||||
|
||||
resp5 = HTTPClient.delete("http://example.com/")
|
||||
resp5.dump
|
||||
|
||||
resp6 = HTTPClient.head("http://example.com/")
|
||||
resp6.body
|
||||
|
||||
resp7 = HTTPClient.options("http://example.com/")
|
||||
resp7.content
|
||||
|
||||
resp8 = HTTPClient.trace("http://example.com/")
|
||||
resp8.http_body
|
||||
|
||||
resp9 = HTTPClient.get_content("http://example.com/")
|
||||
|
||||
resp10 = HTTPClient.post_content("http://example.com/", body: "some_data")
|
||||
@@ -0,0 +1,7 @@
|
||||
| Httparty.rb:5:1:5:35 | call to get | Httparty.rb:5:1:5:35 | call to get |
|
||||
| Httparty.rb:7:1:7:55 | call to post | Httparty.rb:7:1:7:55 | call to post |
|
||||
| Httparty.rb:9:1:9:54 | call to put | Httparty.rb:9:1:9:54 | call to put |
|
||||
| Httparty.rb:11:1:11:56 | call to patch | Httparty.rb:11:1:11:56 | call to patch |
|
||||
| Httparty.rb:15:9:15:46 | call to delete | Httparty.rb:16:1:16:10 | call to body |
|
||||
| Httparty.rb:18:9:18:44 | call to head | Httparty.rb:19:1:19:10 | call to body |
|
||||
| Httparty.rb:21:9:21:47 | call to options | Httparty.rb:22:1:22:10 | call to body |
|
||||
@@ -0,0 +1,4 @@
|
||||
import codeql.ruby.frameworks.http_clients.Httparty
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node httpartyRequests(HttpartyRequest e) { result = e.getResponseBody() }
|
||||
31
ql/test/library-tests/frameworks/http_clients/Httparty.rb
Normal file
31
ql/test/library-tests/frameworks/http_clients/Httparty.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
require "httparty"
|
||||
|
||||
# If the response body is not nil or an empty string, it will be parsed and returned directly.
|
||||
|
||||
HTTParty.get("http://example.com/")
|
||||
|
||||
HTTParty.post("http://example.com/", body: "some_data")
|
||||
|
||||
HTTParty.put("http://example.com/", body: "some_data")
|
||||
|
||||
HTTParty.patch("http://example.com/", body: "some_data")
|
||||
|
||||
# Otherwise, `HTTParty::Response` will be returned, which has a `#body` method.
|
||||
|
||||
resp5 = HTTParty.delete("http://example.com/")
|
||||
resp5.body
|
||||
|
||||
resp6 = HTTParty.head("http://example.com/")
|
||||
resp6.body
|
||||
|
||||
resp7 = HTTParty.options("http://example.com/")
|
||||
resp7.body
|
||||
|
||||
# HTTParty methods can also be included in other classes.
|
||||
# This is not yet modelled.
|
||||
|
||||
class MyClient
|
||||
inlcude HTTParty
|
||||
end
|
||||
|
||||
MyClient.get("http://example.com")
|
||||
@@ -1,8 +0,0 @@
|
||||
| NetHTTP.rb:4:1:4:18 | call to get | NetHTTP.rb:4:1:4:18 | call to get |
|
||||
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:7:1:7:9 | call to body |
|
||||
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:8:1:8:14 | call to read_body |
|
||||
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:9:1:9:11 | call to entity |
|
||||
| NetHTTP.rb:13:6:13:17 | call to get | NetHTTP.rb:18:1:18:7 | call to body |
|
||||
| NetHTTP.rb:14:6:14:18 | call to post | NetHTTP.rb:19:1:19:12 | call to read_body |
|
||||
| NetHTTP.rb:15:6:15:17 | call to put | NetHTTP.rb:20:1:20:9 | call to entity |
|
||||
| NetHTTP.rb:24:3:24:33 | call to get | NetHTTP.rb:27:1:27:28 | call to body |
|
||||
@@ -1,4 +0,0 @@
|
||||
import codeql.ruby.frameworks.http_clients.NetHTTP
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node netHTTPRequests(NetHTTPRequest e) { result = e.getResponseBody() }
|
||||
@@ -0,0 +1,8 @@
|
||||
| NetHttp.rb:4:1:4:18 | call to get | NetHttp.rb:4:1:4:18 | call to get |
|
||||
| NetHttp.rb:6:8:6:50 | call to post | NetHttp.rb:7:1:7:9 | call to body |
|
||||
| NetHttp.rb:6:8:6:50 | call to post | NetHttp.rb:8:1:8:14 | call to read_body |
|
||||
| NetHttp.rb:6:8:6:50 | call to post | NetHttp.rb:9:1:9:11 | call to entity |
|
||||
| NetHttp.rb:13:6:13:17 | call to get | NetHttp.rb:18:1:18:7 | call to body |
|
||||
| NetHttp.rb:14:6:14:18 | call to post | NetHttp.rb:19:1:19:12 | call to read_body |
|
||||
| NetHttp.rb:15:6:15:17 | call to put | NetHttp.rb:20:1:20:9 | call to entity |
|
||||
| NetHttp.rb:24:3:24:33 | call to get | NetHttp.rb:27:1:27:28 | call to body |
|
||||
4
ql/test/library-tests/frameworks/http_clients/NetHttp.ql
Normal file
4
ql/test/library-tests/frameworks/http_clients/NetHttp.ql
Normal file
@@ -0,0 +1,4 @@
|
||||
import codeql.ruby.frameworks.http_clients.NetHttp
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node netHttpRequests(NetHttpRequest e) { result = e.getResponseBody() }
|
||||
@@ -0,0 +1,4 @@
|
||||
| OpenURI.rb:3:9:3:41 | call to open | OpenURI.rb:4:1:4:10 | call to read |
|
||||
| OpenURI.rb:6:9:6:34 | call to open | OpenURI.rb:7:1:7:15 | call to readlines |
|
||||
| OpenURI.rb:9:9:9:38 | call to open | OpenURI.rb:10:1:10:10 | call to read |
|
||||
| OpenURI.rb:12:9:12:45 | call to open | OpenURI.rb:13:1:13:10 | call to read |
|
||||
4
ql/test/library-tests/frameworks/http_clients/OpenURI.ql
Normal file
4
ql/test/library-tests/frameworks/http_clients/OpenURI.ql
Normal file
@@ -0,0 +1,4 @@
|
||||
import codeql.ruby.frameworks.http_clients.OpenURI
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node openURIRequests(OpenURIRequest e) { result = e.getResponseBody() }
|
||||
13
ql/test/library-tests/frameworks/http_clients/OpenURI.rb
Normal file
13
ql/test/library-tests/frameworks/http_clients/OpenURI.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
require "open-uri"
|
||||
|
||||
resp1 = Kernel.open("http://example.com")
|
||||
resp1.read
|
||||
|
||||
resp2 = open("http://example.com")
|
||||
resp2.readlines
|
||||
|
||||
resp3 = URI.open("http://example.com")
|
||||
resp3.read
|
||||
|
||||
resp4 = URI.parse("https://example.com").open
|
||||
resp4.read
|
||||
@@ -1,6 +1,6 @@
|
||||
import codeql.ruby.frameworks.http_clients.RestClient
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node restClientHTTPRequests(RestClientHTTPRequest e) {
|
||||
query DataFlow::Node restClientHttpRequests(RestClientHttpRequest e) {
|
||||
result = e.getResponseBody()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
| Typhoeus.rb:3:9:3:43 | call to get | Typhoeus.rb:4:1:4:10 | call to body |
|
||||
| Typhoeus.rb:6:9:6:63 | call to post | Typhoeus.rb:7:1:7:10 | call to body |
|
||||
| Typhoeus.rb:9:9:9:62 | call to put | Typhoeus.rb:10:1:10:10 | call to body |
|
||||
| Typhoeus.rb:12:9:12:64 | call to patch | Typhoeus.rb:13:1:13:10 | call to body |
|
||||
| Typhoeus.rb:15:9:15:46 | call to delete | Typhoeus.rb:16:1:16:10 | call to body |
|
||||
| Typhoeus.rb:18:9:18:44 | call to head | Typhoeus.rb:19:1:19:10 | call to body |
|
||||
| Typhoeus.rb:21:9:21:47 | call to options | Typhoeus.rb:22:1:22:10 | call to body |
|
||||
@@ -0,0 +1,4 @@
|
||||
import codeql.ruby.frameworks.http_clients.Typhoeus
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query DataFlow::Node typhoeusHttpRequests(TyphoeusHttpRequest e) { result = e.getResponseBody() }
|
||||
22
ql/test/library-tests/frameworks/http_clients/Typhoeus.rb
Normal file
22
ql/test/library-tests/frameworks/http_clients/Typhoeus.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
require "typhoeus"
|
||||
|
||||
resp1 = Typhoeus.get("http://example.com/")
|
||||
resp1.body
|
||||
|
||||
resp2 = Typhoeus.post("http://example.com/", body: "some_data")
|
||||
resp2.body
|
||||
|
||||
resp3 = Typhoeus.put("http://example.com/", body: "some_data")
|
||||
resp3.body
|
||||
|
||||
resp4 = Typhoeus.patch("http://example.com/", body: "some_data")
|
||||
resp4.body
|
||||
|
||||
resp5 = Typhoeus.delete("http://example.com/")
|
||||
resp5.body
|
||||
|
||||
resp6 = Typhoeus.head("http://example.com/")
|
||||
resp6.body
|
||||
|
||||
resp7 = Typhoeus.options("http://example.com/")
|
||||
resp7.body
|
||||
11
ql/test/query-tests/security/cwe-078/KernelOpen.expected
Normal file
11
ql/test/query-tests/security/cwe-078/KernelOpen.expected
Normal file
@@ -0,0 +1,11 @@
|
||||
edges
|
||||
| KernelOpen.rb:3:12:3:17 | call to params : | KernelOpen.rb:4:10:4:13 | file |
|
||||
| KernelOpen.rb:3:12:3:17 | call to params : | KernelOpen.rb:5:13:5:16 | file |
|
||||
nodes
|
||||
| KernelOpen.rb:3:12:3:17 | call to params : | semmle.label | call to params : |
|
||||
| KernelOpen.rb:4:10:4:13 | file | semmle.label | file |
|
||||
| KernelOpen.rb:5:13:5:16 | file | semmle.label | file |
|
||||
subpaths
|
||||
#select
|
||||
| KernelOpen.rb:4:10:4:13 | file | KernelOpen.rb:3:12:3:17 | call to params : | KernelOpen.rb:4:10:4:13 | file | This call to Kernel.open depends on a user-provided value. Replace it with File.open. |
|
||||
| KernelOpen.rb:5:13:5:16 | file | KernelOpen.rb:3:12:3:17 | call to params : | KernelOpen.rb:5:13:5:16 | file | This call to IO.read depends on a user-provided value. Replace it with File.read. |
|
||||
1
ql/test/query-tests/security/cwe-078/KernelOpen.qlref
Normal file
1
ql/test/query-tests/security/cwe-078/KernelOpen.qlref
Normal file
@@ -0,0 +1 @@
|
||||
queries/security/cwe-078/KernelOpen.ql
|
||||
17
ql/test/query-tests/security/cwe-078/KernelOpen.rb
Normal file
17
ql/test/query-tests/security/cwe-078/KernelOpen.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class UsersController < ActionController::Base
|
||||
def create
|
||||
file = params[:file]
|
||||
open(file) # BAD
|
||||
IO.read(file) # BAD
|
||||
|
||||
File.open(file).read # GOOD
|
||||
|
||||
if file == "some/const/path.txt"
|
||||
open(file) # GOOD - file path is sanitised by guard
|
||||
end
|
||||
|
||||
if %w(some/const/1.txt some/const/2.txt).include? file
|
||||
IO.read(file) # GOOD - file path is sanitised by guard
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -17,10 +17,10 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
class Admin < User
|
||||
def self.delete_all(condition = nil)
|
||||
# BAD: `delete_all` overrides an ActiveRecord method, but doesn't perform
|
||||
def self.delete_by(condition = nil)
|
||||
# BAD: `delete_by overrides an ActiveRecord method, but doesn't perform
|
||||
# any validation before passing its arguments on to another ActiveRecord method
|
||||
destroy_all(condition)
|
||||
destroy_by(condition)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,12 +34,26 @@ class FooController < ActionController::Base
|
||||
# where `params[:column]` is unsanitized
|
||||
User.calculate(:average, params[:column])
|
||||
|
||||
# BAD: executes `SELECT MAX(#{params[:column]}) FROM "users"`
|
||||
# where `params[:column]` is unsanitized
|
||||
User.maximum(params[:column])
|
||||
|
||||
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
|
||||
# where `params[:id]` is unsanitized
|
||||
User.delete_by("id = '#{params[:id]}'")
|
||||
|
||||
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
|
||||
# where `params[:id]` is unsanitized
|
||||
# (in Rails < 4.0)
|
||||
User.delete_all("id = '#{params[:id]}'")
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
|
||||
# where `params[:id]` is unsanitized
|
||||
User.destroy_by(["id = '#{params[:id]}'"])
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
|
||||
# where `params[:id]` is unsanitized
|
||||
# (in Rails < 4.0)
|
||||
User.destroy_all(["id = '#{params[:id]}'"])
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE id BETWEEN '#{params[:min_id]}' AND 100000`
|
||||
@@ -54,6 +68,28 @@ class FooController < ActionController::Base
|
||||
User.where.not("user.id = '#{params[:id]}'")
|
||||
|
||||
User.authenticate(params[:name], params[:pass])
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')` LIMIT 1
|
||||
# where `params[:id]` is unsanitized
|
||||
User.find_or_initialize_by("id = '#{params[:id]}'")
|
||||
|
||||
user = User.first
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE id = 1 LIMIT 1 #{params[:lock]}`
|
||||
# where `params[:lock]` is unsanitized
|
||||
user.reload(lock: params[:lock])
|
||||
|
||||
# BAD: executes `SELECT #{params[:column]} FROM "users"`
|
||||
# where `params[:column]` is unsanitized
|
||||
User.select(params[:column])
|
||||
User.reselect(params[:column])
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE (#{params[:condition]})`
|
||||
# where `params[:condition]` is unsanitized
|
||||
User.rewhere(params[:condition])
|
||||
|
||||
# BAD: executes `UPDATE "users" SET #{params[:fields]}`
|
||||
# where `params[:fields]` is unsanitized
|
||||
User.update_all(params[:fields])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,7 +101,7 @@ class BarController < ApplicationController
|
||||
|
||||
# BAD: executes `DELETE FROM "users" WHERE (id = #{uid})`
|
||||
# where `uid` is unsantized
|
||||
User.delete_all("id " + uidEq)
|
||||
User.delete_by("id " + uidEq)
|
||||
end
|
||||
|
||||
def safe_paths
|
||||
@@ -98,6 +134,6 @@ end
|
||||
|
||||
class BazController < BarController
|
||||
def yet_another_handler
|
||||
Admin.delete_all(params[:admin_condition])
|
||||
Admin.delete_by(params[:admin_condition])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,51 +1,83 @@
|
||||
edges
|
||||
| ActiveRecordInjection.rb:8:25:8:28 | name : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" |
|
||||
| ActiveRecordInjection.rb:8:31:8:34 | pass : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" |
|
||||
| ActiveRecordInjection.rb:20:23:20:31 | condition : | ActiveRecordInjection.rb:23:17:23:25 | condition |
|
||||
| ActiveRecordInjection.rb:20:22:20:30 | condition : | ActiveRecordInjection.rb:23:16:23:24 | condition |
|
||||
| ActiveRecordInjection.rb:35:30:35:35 | call to params : | ActiveRecordInjection.rb:35:30:35:44 | ...[...] |
|
||||
| ActiveRecordInjection.rb:39:30:39:35 | call to params : | ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:43:32:43:37 | call to params : | ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:48:21:48:26 | call to params : | ActiveRecordInjection.rb:47:16:47:21 | <<-SQL |
|
||||
| ActiveRecordInjection.rb:54:34:54:39 | call to params : | ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:56:23:56:28 | call to params : | ActiveRecordInjection.rb:56:23:56:35 | ...[...] : |
|
||||
| ActiveRecordInjection.rb:56:23:56:35 | ...[...] : | ActiveRecordInjection.rb:8:25:8:28 | name : |
|
||||
| ActiveRecordInjection.rb:56:38:56:43 | call to params : | ActiveRecordInjection.rb:56:38:56:50 | ...[...] : |
|
||||
| ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | ActiveRecordInjection.rb:8:31:8:34 | pass : |
|
||||
| ActiveRecordInjection.rb:62:10:62:15 | call to params : | ActiveRecordInjection.rb:68:21:68:33 | ... + ... |
|
||||
| ActiveRecordInjection.rb:101:22:101:27 | call to params : | ActiveRecordInjection.rb:101:22:101:45 | ...[...] : |
|
||||
| ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | ActiveRecordInjection.rb:20:23:20:31 | condition : |
|
||||
| ActiveRecordInjection.rb:39:18:39:23 | call to params : | ActiveRecordInjection.rb:39:18:39:32 | ...[...] |
|
||||
| ActiveRecordInjection.rb:43:29:43:34 | call to params : | ActiveRecordInjection.rb:43:20:43:42 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:48:30:48:35 | call to params : | ActiveRecordInjection.rb:48:21:48:43 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:52:31:52:36 | call to params : | ActiveRecordInjection.rb:52:22:52:44 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:57:32:57:37 | call to params : | ActiveRecordInjection.rb:57:23:57:45 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:62:21:62:26 | call to params : | ActiveRecordInjection.rb:61:16:61:21 | <<-SQL |
|
||||
| ActiveRecordInjection.rb:68:34:68:39 | call to params : | ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:70:23:70:28 | call to params : | ActiveRecordInjection.rb:70:23:70:35 | ...[...] : |
|
||||
| ActiveRecordInjection.rb:70:23:70:35 | ...[...] : | ActiveRecordInjection.rb:8:25:8:28 | name : |
|
||||
| ActiveRecordInjection.rb:70:38:70:43 | call to params : | ActiveRecordInjection.rb:70:38:70:50 | ...[...] : |
|
||||
| ActiveRecordInjection.rb:70:38:70:50 | ...[...] : | ActiveRecordInjection.rb:8:31:8:34 | pass : |
|
||||
| ActiveRecordInjection.rb:74:41:74:46 | call to params : | ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:83:17:83:22 | call to params : | ActiveRecordInjection.rb:83:17:83:31 | ...[...] |
|
||||
| ActiveRecordInjection.rb:84:19:84:24 | call to params : | ActiveRecordInjection.rb:84:19:84:33 | ...[...] |
|
||||
| ActiveRecordInjection.rb:88:18:88:23 | call to params : | ActiveRecordInjection.rb:88:18:88:35 | ...[...] |
|
||||
| ActiveRecordInjection.rb:92:21:92:26 | call to params : | ActiveRecordInjection.rb:92:21:92:35 | ...[...] |
|
||||
| ActiveRecordInjection.rb:98:10:98:15 | call to params : | ActiveRecordInjection.rb:104:20:104:32 | ... + ... |
|
||||
| ActiveRecordInjection.rb:137:21:137:26 | call to params : | ActiveRecordInjection.rb:137:21:137:44 | ...[...] : |
|
||||
| ActiveRecordInjection.rb:137:21:137:44 | ...[...] : | ActiveRecordInjection.rb:20:22:20:30 | condition : |
|
||||
nodes
|
||||
| ActiveRecordInjection.rb:8:25:8:28 | name : | semmle.label | name : |
|
||||
| ActiveRecordInjection.rb:8:31:8:34 | pass : | semmle.label | pass : |
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | semmle.label | "name='#{...}' and pass='#{...}'" |
|
||||
| ActiveRecordInjection.rb:20:23:20:31 | condition : | semmle.label | condition : |
|
||||
| ActiveRecordInjection.rb:23:17:23:25 | condition | semmle.label | condition |
|
||||
| ActiveRecordInjection.rb:20:22:20:30 | condition : | semmle.label | condition : |
|
||||
| ActiveRecordInjection.rb:23:16:23:24 | condition | semmle.label | condition |
|
||||
| ActiveRecordInjection.rb:35:30:35:35 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:35:30:35:44 | ...[...] | semmle.label | ...[...] |
|
||||
| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:39:30:39:35 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:43:32:43:37 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | semmle.label | <<-SQL |
|
||||
| ActiveRecordInjection.rb:48:21:48:26 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | semmle.label | "user.id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:54:34:54:39 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:56:23:56:28 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:56:23:56:35 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ActiveRecordInjection.rb:56:38:56:43 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ActiveRecordInjection.rb:62:10:62:15 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:68:21:68:33 | ... + ... | semmle.label | ... + ... |
|
||||
| ActiveRecordInjection.rb:101:22:101:27 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ActiveRecordInjection.rb:39:18:39:23 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:39:18:39:32 | ...[...] | semmle.label | ...[...] |
|
||||
| ActiveRecordInjection.rb:43:20:43:42 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:43:29:43:34 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:48:21:48:43 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:48:30:48:35 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:52:22:52:44 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:52:31:52:36 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:57:23:57:45 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:57:32:57:37 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:61:16:61:21 | <<-SQL | semmle.label | <<-SQL |
|
||||
| ActiveRecordInjection.rb:62:21:62:26 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" | semmle.label | "user.id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:68:34:68:39 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:70:23:70:28 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:70:23:70:35 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ActiveRecordInjection.rb:70:38:70:43 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:70:38:70:50 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:74:41:74:46 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:83:17:83:22 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:83:17:83:31 | ...[...] | semmle.label | ...[...] |
|
||||
| ActiveRecordInjection.rb:84:19:84:24 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:84:19:84:33 | ...[...] | semmle.label | ...[...] |
|
||||
| ActiveRecordInjection.rb:88:18:88:23 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:88:18:88:35 | ...[...] | semmle.label | ...[...] |
|
||||
| ActiveRecordInjection.rb:92:21:92:26 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:92:21:92:35 | ...[...] | semmle.label | ...[...] |
|
||||
| ActiveRecordInjection.rb:98:10:98:15 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:104:20:104:32 | ... + ... | semmle.label | ... + ... |
|
||||
| ActiveRecordInjection.rb:137:21:137:26 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:137:21:137:44 | ...[...] : | semmle.label | ...[...] : |
|
||||
subpaths
|
||||
#select
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:56:23:56:28 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:56:23:56:28 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:56:38:56:43 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:56:38:56:43 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:23:17:23:25 | condition | ActiveRecordInjection.rb:101:22:101:27 | call to params : | ActiveRecordInjection.rb:23:17:23:25 | condition | This SQL query depends on $@. | ActiveRecordInjection.rb:101:22:101:27 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:70:23:70:28 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:70:23:70:28 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:70:38:70:43 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:70:38:70:43 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:23:16:23:24 | condition | ActiveRecordInjection.rb:137:21:137:26 | call to params : | ActiveRecordInjection.rb:23:16:23:24 | condition | This SQL query depends on $@. | ActiveRecordInjection.rb:137:21:137:26 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:35:30:35:44 | ...[...] | ActiveRecordInjection.rb:35:30:35:35 | call to params : | ActiveRecordInjection.rb:35:30:35:44 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:35:30:35:35 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | ActiveRecordInjection.rb:39:30:39:35 | call to params : | ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:39:30:39:35 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | ActiveRecordInjection.rb:43:32:43:37 | call to params : | ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:43:32:43:37 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | ActiveRecordInjection.rb:48:21:48:26 | call to params : | ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | This SQL query depends on $@. | ActiveRecordInjection.rb:48:21:48:26 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | ActiveRecordInjection.rb:54:34:54:39 | call to params : | ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:54:34:54:39 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:68:21:68:33 | ... + ... | ActiveRecordInjection.rb:62:10:62:15 | call to params : | ActiveRecordInjection.rb:68:21:68:33 | ... + ... | This SQL query depends on $@. | ActiveRecordInjection.rb:62:10:62:15 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:39:18:39:32 | ...[...] | ActiveRecordInjection.rb:39:18:39:23 | call to params : | ActiveRecordInjection.rb:39:18:39:32 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:39:18:39:23 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:43:20:43:42 | "id = '#{...}'" | ActiveRecordInjection.rb:43:29:43:34 | call to params : | ActiveRecordInjection.rb:43:20:43:42 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:43:29:43:34 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:48:21:48:43 | "id = '#{...}'" | ActiveRecordInjection.rb:48:30:48:35 | call to params : | ActiveRecordInjection.rb:48:21:48:43 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:48:30:48:35 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:52:22:52:44 | "id = '#{...}'" | ActiveRecordInjection.rb:52:31:52:36 | call to params : | ActiveRecordInjection.rb:52:22:52:44 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:52:31:52:36 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:57:23:57:45 | "id = '#{...}'" | ActiveRecordInjection.rb:57:32:57:37 | call to params : | ActiveRecordInjection.rb:57:23:57:45 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:57:32:57:37 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:61:16:61:21 | <<-SQL | ActiveRecordInjection.rb:62:21:62:26 | call to params : | ActiveRecordInjection.rb:61:16:61:21 | <<-SQL | This SQL query depends on $@. | ActiveRecordInjection.rb:62:21:62:26 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" | ActiveRecordInjection.rb:68:34:68:39 | call to params : | ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:68:34:68:39 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | ActiveRecordInjection.rb:74:41:74:46 | call to params : | ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:74:41:74:46 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:83:17:83:31 | ...[...] | ActiveRecordInjection.rb:83:17:83:22 | call to params : | ActiveRecordInjection.rb:83:17:83:31 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:83:17:83:22 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:84:19:84:33 | ...[...] | ActiveRecordInjection.rb:84:19:84:24 | call to params : | ActiveRecordInjection.rb:84:19:84:33 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:84:19:84:24 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:88:18:88:35 | ...[...] | ActiveRecordInjection.rb:88:18:88:23 | call to params : | ActiveRecordInjection.rb:88:18:88:35 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:88:18:88:23 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:92:21:92:35 | ...[...] | ActiveRecordInjection.rb:92:21:92:26 | call to params : | ActiveRecordInjection.rb:92:21:92:35 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:92:21:92:26 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:104:20:104:32 | ... + ... | ActiveRecordInjection.rb:98:10:98:15 | call to params : | ActiveRecordInjection.rb:104:20:104:32 | ... + ... | This SQL query depends on $@. | ActiveRecordInjection.rb:98:10:98:15 | call to params | a user-provided value |
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
edges
|
||||
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:6:10:6:13 | code |
|
||||
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:18:20:18:23 | code |
|
||||
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:21:21:21:24 | code |
|
||||
nodes
|
||||
| CodeInjection.rb:3:12:3:17 | call to params : | semmle.label | call to params : |
|
||||
| CodeInjection.rb:6:10:6:13 | code | semmle.label | code |
|
||||
| CodeInjection.rb:9:10:9:15 | call to params | semmle.label | call to params |
|
||||
| CodeInjection.rb:18:20:18:23 | code | semmle.label | code |
|
||||
| CodeInjection.rb:21:21:21:24 | code | semmle.label | code |
|
||||
subpaths
|
||||
#select
|
||||
| CodeInjection.rb:6:10:6:13 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:6:10:6:13 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |
|
||||
| CodeInjection.rb:9:10:9:15 | call to params | CodeInjection.rb:9:10:9:15 | call to params | CodeInjection.rb:9:10:9:15 | call to params | This code execution depends on $@. | CodeInjection.rb:9:10:9:15 | call to params | a user-provided value |
|
||||
| CodeInjection.rb:18:20:18:23 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:18:20:18:23 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |
|
||||
| CodeInjection.rb:21:21:21:24 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:21:21:21:24 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |
|
||||
|
||||
@@ -8,14 +8,32 @@ class UsersController < ActionController::Base
|
||||
# BAD
|
||||
eval(params)
|
||||
|
||||
# GOOD - user input is in second argument, which is not evaluated as Ruby code
|
||||
send(:sanitize, params[:code])
|
||||
|
||||
# GOOD
|
||||
Foo.new.bar(code)
|
||||
|
||||
# BAD
|
||||
Foo.class_eval(code)
|
||||
|
||||
# BAD
|
||||
Foo.module_eval(code)
|
||||
|
||||
# GOOD
|
||||
Bar.class_eval(code)
|
||||
end
|
||||
|
||||
def update
|
||||
# GOOD
|
||||
eval("foo")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sanitize(code)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class Foo
|
||||
@@ -27,3 +45,9 @@ class Foo
|
||||
eval(x)
|
||||
end
|
||||
end
|
||||
|
||||
class Bar
|
||||
def self.class_eval(x)
|
||||
true
|
||||
end
|
||||
end
|
||||
@@ -3,9 +3,7 @@ edges
|
||||
| UrlRedirect.rb:14:17:14:22 | call to params : | UrlRedirect.rb:14:17:14:43 | call to fetch |
|
||||
| UrlRedirect.rb:19:17:19:22 | call to params : | UrlRedirect.rb:19:17:19:37 | call to to_unsafe_hash |
|
||||
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:24:17:24:37 | call to filter_params |
|
||||
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:56:21:56:32 | input_params : |
|
||||
| UrlRedirect.rb:34:20:34:25 | call to params : | UrlRedirect.rb:34:17:34:37 | "#{...}/foo" |
|
||||
| UrlRedirect.rb:56:21:56:32 | input_params : | UrlRedirect.rb:57:5:57:29 | call to permit : |
|
||||
nodes
|
||||
| UrlRedirect.rb:4:17:4:22 | call to params | semmle.label | call to params |
|
||||
| UrlRedirect.rb:9:17:9:22 | call to params : | semmle.label | call to params : |
|
||||
@@ -18,10 +16,7 @@ nodes
|
||||
| UrlRedirect.rb:24:31:24:36 | call to params : | semmle.label | call to params : |
|
||||
| UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | semmle.label | "#{...}/foo" |
|
||||
| UrlRedirect.rb:34:20:34:25 | call to params : | semmle.label | call to params : |
|
||||
| UrlRedirect.rb:56:21:56:32 | input_params : | semmle.label | input_params : |
|
||||
| UrlRedirect.rb:57:5:57:29 | call to permit : | semmle.label | call to permit : |
|
||||
subpaths
|
||||
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:56:21:56:32 | input_params : | UrlRedirect.rb:57:5:57:29 | call to permit : | UrlRedirect.rb:24:17:24:37 | call to filter_params : |
|
||||
#select
|
||||
| UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | Untrusted URL redirection due to $@. | UrlRedirect.rb:4:17:4:22 | call to params | a user-provided value |
|
||||
| UrlRedirect.rb:9:17:9:28 | ...[...] | UrlRedirect.rb:9:17:9:22 | call to params : | UrlRedirect.rb:9:17:9:28 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:9:17:9:22 | call to params | a user-provided value |
|
||||
|
||||
16
ql/test/query-tests/security/cwe-611/LibXmlRuby.rb
Normal file
16
ql/test/query-tests/security/cwe-611/LibXmlRuby.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
class LibXmlRubyXXE < ApplicationController
|
||||
|
||||
content = params[:xml]
|
||||
LibXML::XML::Document.string(content, { options: 2 | 2048, encoding: 'utf-8' })
|
||||
LibXML::XML::Document.file(content, { options: LibXML::XML::Parser::Options::NOENT | 2048})
|
||||
LibXML::XML::Document.io(content, { options: XML::Parser::Options::NOENT | 2048 })
|
||||
LibXML::XML::Parser.string(content, { options: 2 | 2048 })
|
||||
LibXML::XML::Parser.file(content, { options: 3 | 2048 })
|
||||
LibXML::XML::Parser.io(content, { options: 2 | 2048})
|
||||
|
||||
XML::Document.string(content, { options: 2 | 2048 })
|
||||
XML::Parser.string(content, { options: 2 | 2048 })
|
||||
|
||||
LibXML::XML::Parser.file(content, { options: 2048 }) # OK
|
||||
|
||||
end
|
||||
30
ql/test/query-tests/security/cwe-611/Nokogiri.rb
Normal file
30
ql/test/query-tests/security/cwe-611/Nokogiri.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
class NokogiriXXE < ApplicationController
|
||||
|
||||
content = params[:xml]
|
||||
|
||||
Nokogiri::XML::parse(content, nil, nil, 2)
|
||||
Nokogiri::XML::parse(content, nil, nil, 1 | 2)
|
||||
Nokogiri::XML::parse(content, nil, nil, 1 & ~Nokogiri::XML::ParseOptions::NONET)
|
||||
Nokogiri::XML::parse(content, nil, nil, Nokogiri::XML::ParseOptions::NOENT)
|
||||
Nokogiri::XML::parse(content, nil, nil, Nokogiri::XML::ParseOptions::DTDLOAD)
|
||||
Nokogiri::XML::parse(content, nil, nil, ~Nokogiri::XML::ParseOptions::NOENT) #OK
|
||||
Nokogiri::XML::parse(content, nil, nil, ~Nokogiri::XML::ParseOptions::NONET)
|
||||
Nokogiri::XML::parse(content, nil, nil, Nokogiri::XML::ParseOptions.new 2)
|
||||
options = Nokogiri::XML::ParseOptions.new 2048
|
||||
options.noent
|
||||
Nokogiri::XML::parse(content, nil, nil, options)
|
||||
Nokogiri::XML::parse(content, nil, nil, (Nokogiri::XML::ParseOptions.new 0).noent)
|
||||
|
||||
Nokogiri::XML::parse(content) { |x| x.noent }
|
||||
Nokogiri::XML::parse(content) { |x| x.nononet } #FAIL
|
||||
Nokogiri::XML::parse(content) { |x| x.nodtdload } # OK
|
||||
|
||||
Nokogiri::XML::parse(content) { |x| x.nonet.noent.nodtdload }
|
||||
|
||||
Nokogiri::XML::parse(content, nil, nil, 2048) # OK
|
||||
Nokogiri::XML::parse(content, nil, nil, 3)
|
||||
Nokogiri::XML::parse(content) { |x| x.nonet.nodtdload } # OK
|
||||
Nokogiri::XML::parse(content, nil, nil, Nokogiri::XML::ParseOptions::NOENT & ~Nokogiri::XML::ParseOptions::NOBLANKS)
|
||||
Nokogiri::XML::parse(content, nil, nil, ~Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::NOBLANKS)
|
||||
|
||||
end
|
||||
75
ql/test/query-tests/security/cwe-611/Xxe.expected
Normal file
75
ql/test/query-tests/security/cwe-611/Xxe.expected
Normal file
@@ -0,0 +1,75 @@
|
||||
edges
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:4:34:4:40 | content |
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:5:32:5:38 | content |
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:6:30:6:36 | content |
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:7:32:7:38 | content |
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:8:30:8:36 | content |
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:9:28:9:34 | content |
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:11:26:11:32 | content |
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:12:24:12:30 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:5:26:5:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:6:26:6:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:7:26:7:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:8:26:8:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:9:26:9:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:11:26:11:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:12:26:12:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:15:26:15:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:16:26:16:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:18:26:18:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:19:26:19:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:22:26:22:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:25:26:25:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:27:26:27:32 | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:28:26:28:32 | content |
|
||||
nodes
|
||||
| LibXmlRuby.rb:3:15:3:20 | call to params : | semmle.label | call to params : |
|
||||
| LibXmlRuby.rb:4:34:4:40 | content | semmle.label | content |
|
||||
| LibXmlRuby.rb:5:32:5:38 | content | semmle.label | content |
|
||||
| LibXmlRuby.rb:6:30:6:36 | content | semmle.label | content |
|
||||
| LibXmlRuby.rb:7:32:7:38 | content | semmle.label | content |
|
||||
| LibXmlRuby.rb:8:30:8:36 | content | semmle.label | content |
|
||||
| LibXmlRuby.rb:9:28:9:34 | content | semmle.label | content |
|
||||
| LibXmlRuby.rb:11:26:11:32 | content | semmle.label | content |
|
||||
| LibXmlRuby.rb:12:24:12:30 | content | semmle.label | content |
|
||||
| Nokogiri.rb:3:15:3:20 | call to params : | semmle.label | call to params : |
|
||||
| Nokogiri.rb:5:26:5:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:6:26:6:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:7:26:7:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:8:26:8:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:9:26:9:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:11:26:11:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:12:26:12:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:15:26:15:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:16:26:16:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:18:26:18:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:19:26:19:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:22:26:22:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:25:26:25:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:27:26:27:32 | content | semmle.label | content |
|
||||
| Nokogiri.rb:28:26:28:32 | content | semmle.label | content |
|
||||
subpaths
|
||||
#select
|
||||
| LibXmlRuby.rb:4:34:4:40 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:4:34:4:40 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
|
||||
| LibXmlRuby.rb:5:32:5:38 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:5:32:5:38 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
|
||||
| LibXmlRuby.rb:6:30:6:36 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:6:30:6:36 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
|
||||
| LibXmlRuby.rb:7:32:7:38 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:7:32:7:38 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
|
||||
| LibXmlRuby.rb:8:30:8:36 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:8:30:8:36 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
|
||||
| LibXmlRuby.rb:9:28:9:34 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:9:28:9:34 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
|
||||
| LibXmlRuby.rb:11:26:11:32 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:11:26:11:32 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
|
||||
| LibXmlRuby.rb:12:24:12:30 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:12:24:12:30 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:5:26:5:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:5:26:5:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:6:26:6:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:6:26:6:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:7:26:7:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:7:26:7:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:8:26:8:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:8:26:8:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:9:26:9:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:9:26:9:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:11:26:11:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:11:26:11:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:12:26:12:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:12:26:12:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:15:26:15:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:15:26:15:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:16:26:16:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:16:26:16:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:18:26:18:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:18:26:18:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:19:26:19:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:19:26:19:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:22:26:22:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:22:26:22:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:25:26:25:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:25:26:25:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:27:26:27:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:27:26:27:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
| Nokogiri.rb:28:26:28:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:28:26:28:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user