mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
Merge branch 'main' into refacReDoS
This commit is contained in:
BIN
ruby/Cargo.lock
generated
BIN
ruby/Cargo.lock
generated
Binary file not shown.
@@ -19,6 +19,7 @@ fn main() -> std::io::Result<()> {
|
||||
.arg("--include-extension=.erb")
|
||||
.arg("--include-extension=.gemspec")
|
||||
.arg("--include=**/Gemfile")
|
||||
.arg("--exclude=**/.git")
|
||||
.arg("--size-limit=5m")
|
||||
.arg("--language=ruby")
|
||||
.arg("--working-dir=.")
|
||||
@@ -29,9 +30,9 @@ fn main() -> std::io::Result<()> {
|
||||
.split('\n')
|
||||
{
|
||||
if let Some(stripped) = line.strip_prefix("include:") {
|
||||
cmd.arg("--include").arg(stripped);
|
||||
cmd.arg("--also-match=".to_owned() + stripped);
|
||||
} else if let Some(stripped) = line.strip_prefix("exclude:") {
|
||||
cmd.arg("--exclude").arg(stripped);
|
||||
cmd.arg("--exclude=".to_owned() + stripped);
|
||||
}
|
||||
}
|
||||
let exit = &cmd.spawn()?.wait()?;
|
||||
|
||||
@@ -11,10 +11,12 @@ flate2 = "1.0"
|
||||
node-types = { path = "../node-types" }
|
||||
tree-sitter = "0.19"
|
||||
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "6334d6ab3d04a5672da695d3b155ca3301511f8d" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "e75d04404c9dd71ad68850d5c672b226d5e694f3" }
|
||||
clap = "3.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
||||
rayon = "1.5.0"
|
||||
num_cpus = "1.13.0"
|
||||
regex = "1.5.5"
|
||||
encoding = "0.2"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
@@ -1,161 +1,112 @@
|
||||
use crate::trap;
|
||||
use node_types::{EntryKind, Field, NodeTypeMap, Storage, TypeName};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap as Map;
|
||||
use std::collections::BTreeSet as Set;
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use tracing::{error, info, span, Level};
|
||||
use tree_sitter::{Language, Node, Parser, Range, Tree};
|
||||
|
||||
pub struct TrapWriter {
|
||||
/// The accumulated trap entries
|
||||
trap_output: Vec<TrapEntry>,
|
||||
/// A counter for generating fresh labels
|
||||
counter: u32,
|
||||
/// cache of global keys
|
||||
global_keys: std::collections::HashMap<String, Label>,
|
||||
pub fn populate_file(writer: &mut trap::Writer, absolute_path: &Path) -> trap::Label {
|
||||
let (file_label, fresh) =
|
||||
writer.global_id(&trap::full_id_for_file(&normalize_path(absolute_path)));
|
||||
if fresh {
|
||||
writer.add_tuple(
|
||||
"files",
|
||||
vec![
|
||||
trap::Arg::Label(file_label),
|
||||
trap::Arg::String(normalize_path(absolute_path)),
|
||||
],
|
||||
);
|
||||
populate_parent_folders(writer, file_label, absolute_path.parent());
|
||||
}
|
||||
file_label
|
||||
}
|
||||
|
||||
pub fn new_trap_writer() -> TrapWriter {
|
||||
TrapWriter {
|
||||
counter: 0,
|
||||
trap_output: Vec::new(),
|
||||
global_keys: std::collections::HashMap::new(),
|
||||
fn populate_empty_file(writer: &mut trap::Writer) -> trap::Label {
|
||||
let (file_label, fresh) = writer.global_id("empty;sourcefile");
|
||||
if fresh {
|
||||
writer.add_tuple(
|
||||
"files",
|
||||
vec![
|
||||
trap::Arg::Label(file_label),
|
||||
trap::Arg::String("".to_string()),
|
||||
],
|
||||
);
|
||||
}
|
||||
file_label
|
||||
}
|
||||
|
||||
impl TrapWriter {
|
||||
/// Gets a label that will hold the unique ID of the passed string at import time.
|
||||
/// This can be used for incrementally importable TRAP files -- use globally unique
|
||||
/// strings to compute a unique ID for table tuples.
|
||||
///
|
||||
/// Note: You probably want to make sure that the key strings that you use are disjoint
|
||||
/// for disjoint column types; the standard way of doing this is to prefix (or append)
|
||||
/// the column type name to the ID. Thus, you might identify methods in Java by the
|
||||
/// full ID "methods_com.method.package.DeclaringClass.method(argumentList)".
|
||||
pub fn populate_empty_location(writer: &mut trap::Writer) {
|
||||
let file_label = populate_empty_file(writer);
|
||||
location(writer, file_label, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
fn fresh_id(&mut self) -> Label {
|
||||
let label = Label(self.counter);
|
||||
self.counter += 1;
|
||||
self.trap_output.push(TrapEntry::FreshId(label));
|
||||
label
|
||||
}
|
||||
|
||||
fn global_id(&mut self, key: &str) -> (Label, bool) {
|
||||
if let Some(label) = self.global_keys.get(key) {
|
||||
return (*label, false);
|
||||
}
|
||||
let label = Label(self.counter);
|
||||
self.counter += 1;
|
||||
self.global_keys.insert(key.to_owned(), label);
|
||||
self.trap_output
|
||||
.push(TrapEntry::MapLabelToKey(label, key.to_owned()));
|
||||
(label, true)
|
||||
}
|
||||
|
||||
fn add_tuple(&mut self, table_name: &str, args: Vec<Arg>) {
|
||||
self.trap_output
|
||||
.push(TrapEntry::GenericTuple(table_name.to_owned(), args))
|
||||
}
|
||||
|
||||
fn populate_file(&mut self, absolute_path: &Path) -> Label {
|
||||
let (file_label, fresh) = self.global_id(&full_id_for_file(absolute_path));
|
||||
if fresh {
|
||||
self.add_tuple(
|
||||
"files",
|
||||
vec![
|
||||
Arg::Label(file_label),
|
||||
Arg::String(normalize_path(absolute_path)),
|
||||
],
|
||||
);
|
||||
self.populate_parent_folders(file_label, absolute_path.parent());
|
||||
}
|
||||
file_label
|
||||
}
|
||||
|
||||
fn populate_empty_file(&mut self) -> Label {
|
||||
let (file_label, fresh) = self.global_id("empty;sourcefile");
|
||||
if fresh {
|
||||
self.add_tuple(
|
||||
"files",
|
||||
vec![Arg::Label(file_label), Arg::String("".to_string())],
|
||||
);
|
||||
}
|
||||
file_label
|
||||
}
|
||||
|
||||
pub fn populate_empty_location(&mut self) {
|
||||
let file_label = self.populate_empty_file();
|
||||
self.location(file_label, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
fn populate_parent_folders(&mut self, child_label: Label, path: Option<&Path>) {
|
||||
let mut path = path;
|
||||
let mut child_label = child_label;
|
||||
loop {
|
||||
match path {
|
||||
None => break,
|
||||
Some(folder) => {
|
||||
let (folder_label, fresh) = self.global_id(&full_id_for_folder(folder));
|
||||
self.add_tuple(
|
||||
"containerparent",
|
||||
vec![Arg::Label(folder_label), Arg::Label(child_label)],
|
||||
pub fn populate_parent_folders(
|
||||
writer: &mut trap::Writer,
|
||||
child_label: trap::Label,
|
||||
path: Option<&Path>,
|
||||
) {
|
||||
let mut path = path;
|
||||
let mut child_label = child_label;
|
||||
loop {
|
||||
match path {
|
||||
None => break,
|
||||
Some(folder) => {
|
||||
let (folder_label, fresh) =
|
||||
writer.global_id(&trap::full_id_for_folder(&normalize_path(folder)));
|
||||
writer.add_tuple(
|
||||
"containerparent",
|
||||
vec![
|
||||
trap::Arg::Label(folder_label),
|
||||
trap::Arg::Label(child_label),
|
||||
],
|
||||
);
|
||||
if fresh {
|
||||
writer.add_tuple(
|
||||
"folders",
|
||||
vec![
|
||||
trap::Arg::Label(folder_label),
|
||||
trap::Arg::String(normalize_path(folder)),
|
||||
],
|
||||
);
|
||||
if fresh {
|
||||
self.add_tuple(
|
||||
"folders",
|
||||
vec![
|
||||
Arg::Label(folder_label),
|
||||
Arg::String(normalize_path(folder)),
|
||||
],
|
||||
);
|
||||
path = folder.parent();
|
||||
child_label = folder_label;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
path = folder.parent();
|
||||
child_label = folder_label;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn location(
|
||||
&mut self,
|
||||
file_label: Label,
|
||||
start_line: usize,
|
||||
start_column: usize,
|
||||
end_line: usize,
|
||||
end_column: usize,
|
||||
) -> Label {
|
||||
let (loc_label, fresh) = self.global_id(&format!(
|
||||
"loc,{{{}}},{},{},{},{}",
|
||||
file_label, start_line, start_column, end_line, end_column
|
||||
));
|
||||
if fresh {
|
||||
self.add_tuple(
|
||||
"locations_default",
|
||||
vec![
|
||||
Arg::Label(loc_label),
|
||||
Arg::Label(file_label),
|
||||
Arg::Int(start_line),
|
||||
Arg::Int(start_column),
|
||||
Arg::Int(end_line),
|
||||
Arg::Int(end_column),
|
||||
],
|
||||
);
|
||||
}
|
||||
loc_label
|
||||
}
|
||||
|
||||
fn comment(&mut self, text: String) {
|
||||
self.trap_output.push(TrapEntry::Comment(text));
|
||||
}
|
||||
|
||||
pub fn output(self, writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
write!(writer, "{}", Program(self.trap_output))
|
||||
fn location(
|
||||
writer: &mut trap::Writer,
|
||||
file_label: trap::Label,
|
||||
start_line: usize,
|
||||
start_column: usize,
|
||||
end_line: usize,
|
||||
end_column: usize,
|
||||
) -> trap::Label {
|
||||
let (loc_label, fresh) = writer.global_id(&format!(
|
||||
"loc,{{{}}},{},{},{},{}",
|
||||
file_label, start_line, start_column, end_line, end_column
|
||||
));
|
||||
if fresh {
|
||||
writer.add_tuple(
|
||||
"locations_default",
|
||||
vec![
|
||||
trap::Arg::Label(loc_label),
|
||||
trap::Arg::Label(file_label),
|
||||
trap::Arg::Int(start_line),
|
||||
trap::Arg::Int(start_column),
|
||||
trap::Arg::Int(end_line),
|
||||
trap::Arg::Int(end_column),
|
||||
],
|
||||
);
|
||||
}
|
||||
loc_label
|
||||
}
|
||||
|
||||
/// Extracts the source file at `path`, which is assumed to be canonicalized.
|
||||
@@ -163,71 +114,43 @@ pub fn extract(
|
||||
language: Language,
|
||||
language_prefix: &str,
|
||||
schema: &NodeTypeMap,
|
||||
trap_writer: &mut TrapWriter,
|
||||
trap_writer: &mut trap::Writer,
|
||||
path: &Path,
|
||||
source: &[u8],
|
||||
ranges: &[Range],
|
||||
) -> std::io::Result<()> {
|
||||
let path_str = format!("{}", path.display());
|
||||
let span = span!(
|
||||
Level::TRACE,
|
||||
"extract",
|
||||
file = %path.display()
|
||||
file = %path_str
|
||||
);
|
||||
|
||||
let _enter = span.enter();
|
||||
|
||||
info!("extracting: {}", path.display());
|
||||
info!("extracting: {}", path_str);
|
||||
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(language).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 {
|
||||
trap_writer.comment(format!("Auto-generated TRAP file for {}", path_str));
|
||||
let file_label = populate_file(trap_writer, path);
|
||||
let mut visitor = Visitor::new(
|
||||
source,
|
||||
trap_writer,
|
||||
// TODO: should we handle path strings that are not valid UTF8 better?
|
||||
path: format!("{}", path.display()),
|
||||
file_label: *file_label,
|
||||
toplevel_child_counter: 0,
|
||||
stack: Vec::new(),
|
||||
&path_str,
|
||||
file_label,
|
||||
language_prefix,
|
||||
schema,
|
||||
};
|
||||
);
|
||||
traverse(&tree, &mut visitor);
|
||||
|
||||
parser.reset();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Escapes a string for use in a TRAP key, by replacing special characters with
|
||||
/// HTML entities.
|
||||
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
|
||||
fn needs_escaping(c: char) -> bool {
|
||||
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
|
||||
}
|
||||
|
||||
let key = key.into();
|
||||
if key.contains(needs_escaping) {
|
||||
let mut escaped = String::with_capacity(2 * key.len());
|
||||
for c in key.chars() {
|
||||
match c {
|
||||
'&' => escaped.push_str("&"),
|
||||
'{' => escaped.push_str("{"),
|
||||
'}' => escaped.push_str("}"),
|
||||
'"' => escaped.push_str("""),
|
||||
'@' => escaped.push_str("@"),
|
||||
'#' => escaped.push_str("#"),
|
||||
_ => escaped.push(c),
|
||||
}
|
||||
}
|
||||
Cow::Owned(escaped)
|
||||
} else {
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes the path according the common CodeQL specification. Assumes that
|
||||
/// `path` has already been canonicalized using `std::fs::canonicalize`.
|
||||
fn normalize_path(path: &Path) -> String {
|
||||
@@ -267,34 +190,28 @@ fn normalize_path(path: &Path) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn full_id_for_file(path: &Path) -> String {
|
||||
format!("{};sourcefile", escape_key(&normalize_path(path)))
|
||||
}
|
||||
|
||||
fn full_id_for_folder(path: &Path) -> String {
|
||||
format!("{};folder", escape_key(&normalize_path(path)))
|
||||
}
|
||||
|
||||
struct ChildNode {
|
||||
field_name: Option<&'static str>,
|
||||
label: Label,
|
||||
label: trap::Label,
|
||||
type_name: TypeName,
|
||||
}
|
||||
|
||||
struct Visitor<'a> {
|
||||
/// The file path of the source code (as string)
|
||||
path: String,
|
||||
path: &'a str,
|
||||
/// The label to use whenever we need to refer to the `@file` entity of this
|
||||
/// source file.
|
||||
file_label: Label,
|
||||
file_label: trap::Label,
|
||||
/// The source code as a UTF-8 byte array
|
||||
source: &'a [u8],
|
||||
/// A TrapWriter to accumulate trap entries
|
||||
trap_writer: &'a mut TrapWriter,
|
||||
/// A trap::Writer to accumulate trap entries
|
||||
trap_writer: &'a mut trap::Writer,
|
||||
/// A counter for top-level child nodes
|
||||
toplevel_child_counter: usize,
|
||||
/// Language prefix
|
||||
language_prefix: &'a str,
|
||||
/// Language-specific name of the AST info table
|
||||
ast_node_info_table_name: String,
|
||||
/// Language-specific name of the tokeninfo table
|
||||
tokeninfo_table_name: String,
|
||||
/// A lookup table from type name to node types
|
||||
schema: &'a NodeTypeMap,
|
||||
/// A stack for gathering information from child nodes. Whenever a node is
|
||||
@@ -303,27 +220,48 @@ struct Visitor<'a> {
|
||||
/// node the list containing the child data is popped from the stack and
|
||||
/// matched against the dbscheme for the node. If the expectations are met
|
||||
/// the corresponding row definitions are added to the trap_output.
|
||||
stack: Vec<(Label, usize, Vec<ChildNode>)>,
|
||||
stack: Vec<(trap::Label, usize, Vec<ChildNode>)>,
|
||||
}
|
||||
|
||||
impl Visitor<'_> {
|
||||
impl<'a> Visitor<'a> {
|
||||
fn new(
|
||||
source: &'a [u8],
|
||||
trap_writer: &'a mut trap::Writer,
|
||||
path: &'a str,
|
||||
file_label: trap::Label,
|
||||
language_prefix: &str,
|
||||
schema: &'a NodeTypeMap,
|
||||
) -> Visitor<'a> {
|
||||
Visitor {
|
||||
path,
|
||||
file_label,
|
||||
source,
|
||||
trap_writer,
|
||||
toplevel_child_counter: 0,
|
||||
ast_node_info_table_name: format!("{}_ast_node_info", language_prefix),
|
||||
tokeninfo_table_name: format!("{}_tokeninfo", language_prefix),
|
||||
schema,
|
||||
stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn record_parse_error(
|
||||
&mut self,
|
||||
error_message: String,
|
||||
full_error_message: String,
|
||||
loc: Label,
|
||||
loc: trap::Label,
|
||||
) {
|
||||
error!("{}", full_error_message);
|
||||
let id = self.trap_writer.fresh_id();
|
||||
self.trap_writer.add_tuple(
|
||||
"diagnostics",
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Int(40), // severity 40 = error
|
||||
Arg::String("parse_error".to_string()),
|
||||
Arg::String(error_message),
|
||||
Arg::String(full_error_message),
|
||||
Arg::Label(loc),
|
||||
trap::Arg::Label(id),
|
||||
trap::Arg::Int(40), // severity 40 = error
|
||||
trap::Arg::String("parse_error".to_string()),
|
||||
trap::Arg::String(error_message),
|
||||
trap::Arg::String(full_error_message),
|
||||
trap::Arg::Label(loc),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -335,7 +273,8 @@ impl Visitor<'_> {
|
||||
node: Node,
|
||||
) {
|
||||
let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
|
||||
let loc = self.trap_writer.location(
|
||||
let loc = location(
|
||||
self.trap_writer,
|
||||
self.file_label,
|
||||
start_line,
|
||||
start_column,
|
||||
@@ -374,7 +313,8 @@ impl Visitor<'_> {
|
||||
}
|
||||
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 loc = self.trap_writer.location(
|
||||
let loc = location(
|
||||
self.trap_writer,
|
||||
self.file_label,
|
||||
start_line,
|
||||
start_column,
|
||||
@@ -402,19 +342,19 @@ impl Visitor<'_> {
|
||||
match &table.kind {
|
||||
EntryKind::Token { kind_id, .. } => {
|
||||
self.trap_writer.add_tuple(
|
||||
&format!("{}_ast_node_info", self.language_prefix),
|
||||
&self.ast_node_info_table_name,
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Label(parent_id),
|
||||
Arg::Int(parent_index),
|
||||
Arg::Label(loc),
|
||||
trap::Arg::Label(id),
|
||||
trap::Arg::Label(parent_id),
|
||||
trap::Arg::Int(parent_index),
|
||||
trap::Arg::Label(loc),
|
||||
],
|
||||
);
|
||||
self.trap_writer.add_tuple(
|
||||
&format!("{}_tokeninfo", self.language_prefix),
|
||||
&self.tokeninfo_table_name,
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Int(*kind_id),
|
||||
trap::Arg::Label(id),
|
||||
trap::Arg::Int(*kind_id),
|
||||
sliced_source_arg(self.source, node),
|
||||
],
|
||||
);
|
||||
@@ -425,15 +365,15 @@ impl Visitor<'_> {
|
||||
} => {
|
||||
if let Some(args) = self.complex_node(&node, fields, &child_nodes, id) {
|
||||
self.trap_writer.add_tuple(
|
||||
&format!("{}_ast_node_info", self.language_prefix),
|
||||
&self.ast_node_info_table_name,
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Label(parent_id),
|
||||
Arg::Int(parent_index),
|
||||
Arg::Label(loc),
|
||||
trap::Arg::Label(id),
|
||||
trap::Arg::Label(parent_id),
|
||||
trap::Arg::Int(parent_index),
|
||||
trap::Arg::Label(loc),
|
||||
],
|
||||
);
|
||||
let mut all_args = vec![Arg::Label(id)];
|
||||
let mut all_args = vec![trap::Arg::Label(id)];
|
||||
all_args.extend(args);
|
||||
self.trap_writer.add_tuple(table_name, all_args);
|
||||
}
|
||||
@@ -472,9 +412,9 @@ impl Visitor<'_> {
|
||||
node: &Node,
|
||||
fields: &[Field],
|
||||
child_nodes: &[ChildNode],
|
||||
parent_id: Label,
|
||||
) -> Option<Vec<Arg>> {
|
||||
let mut map: Map<&Option<String>, (&Field, Vec<Arg>)> = Map::new();
|
||||
parent_id: trap::Label,
|
||||
) -> Option<Vec<trap::Arg>> {
|
||||
let mut map: Map<&Option<String>, (&Field, Vec<trap::Arg>)> = Map::new();
|
||||
for field in fields {
|
||||
map.insert(&field.name, (field, Vec::new()));
|
||||
}
|
||||
@@ -488,9 +428,9 @@ impl Visitor<'_> {
|
||||
{
|
||||
// We can safely unwrap because type_matches checks the key is in the map.
|
||||
let (int_value, _) = int_mapping.get(&child_node.type_name.kind).unwrap();
|
||||
values.push(Arg::Int(*int_value));
|
||||
values.push(trap::Arg::Int(*int_value));
|
||||
} else {
|
||||
values.push(Arg::Label(child_node.label));
|
||||
values.push(trap::Arg::Label(child_node.label));
|
||||
}
|
||||
} else if field.name.is_some() {
|
||||
let error_message = format!(
|
||||
@@ -569,9 +509,9 @@ impl Visitor<'_> {
|
||||
);
|
||||
break;
|
||||
}
|
||||
let mut args = vec![Arg::Label(parent_id)];
|
||||
let mut args = vec![trap::Arg::Label(parent_id)];
|
||||
if *has_index {
|
||||
args.push(Arg::Int(index))
|
||||
args.push(trap::Arg::Int(index))
|
||||
}
|
||||
args.push(child_value.clone());
|
||||
self.trap_writer.add_tuple(table_name, args);
|
||||
@@ -625,9 +565,9 @@ impl Visitor<'_> {
|
||||
}
|
||||
|
||||
// Emit a slice of a source file as an Arg.
|
||||
fn sliced_source_arg(source: &[u8], n: Node) -> Arg {
|
||||
fn sliced_source_arg(source: &[u8], n: Node) -> trap::Arg {
|
||||
let range = n.byte_range();
|
||||
Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned())
|
||||
trap::Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned())
|
||||
}
|
||||
|
||||
// Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated.
|
||||
@@ -699,59 +639,6 @@ fn traverse(tree: &Tree, visitor: &mut Visitor) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Program(Vec<TrapEntry>);
|
||||
|
||||
impl fmt::Display for Program {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut text = String::new();
|
||||
for trap_entry in &self.0 {
|
||||
text.push_str(&format!("{}\n", trap_entry));
|
||||
}
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
enum TrapEntry {
|
||||
/// Maps the label to a fresh id, e.g. `#123=*`.
|
||||
FreshId(Label),
|
||||
/// Maps the label to a key, e.g. `#7=@"foo"`.
|
||||
MapLabelToKey(Label, String),
|
||||
/// foo_bar(arg*)
|
||||
GenericTuple(String, Vec<Arg>),
|
||||
Comment(String),
|
||||
}
|
||||
impl fmt::Display for TrapEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TrapEntry::FreshId(label) => write!(f, "{}=*", label),
|
||||
TrapEntry::MapLabelToKey(label, key) => {
|
||||
write!(f, "{}=@\"{}\"", label, key.replace("\"", "\"\""))
|
||||
}
|
||||
TrapEntry::GenericTuple(name, args) => {
|
||||
write!(f, "{}(", name)?;
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ",")?;
|
||||
}
|
||||
write!(f, "{}", arg)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
TrapEntry::Comment(line) => write!(f, "// {}", line),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
// Identifiers of the form #0, #1...
|
||||
struct Label(u32);
|
||||
|
||||
impl fmt::Display for Label {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "#{:x}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric indices.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Index(usize);
|
||||
@@ -761,69 +648,3 @@ impl fmt::Display for Index {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Some untyped argument to a TrapEntry.
|
||||
#[derive(Debug, Clone)]
|
||||
enum Arg {
|
||||
Label(Label),
|
||||
Int(usize),
|
||||
String(String),
|
||||
}
|
||||
|
||||
const MAX_STRLEN: usize = 1048576;
|
||||
|
||||
impl fmt::Display for Arg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Arg::Label(x) => write!(f, "{}", x),
|
||||
Arg::Int(x) => write!(f, "{}", x),
|
||||
Arg::String(x) => write!(
|
||||
f,
|
||||
"\"{}\"",
|
||||
limit_string(x, MAX_STRLEN).replace("\"", "\"\"")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Limit the length (in bytes) of a string. If the string's length in bytes is
|
||||
/// less than or equal to the limit then the entire string is returned. Otherwise
|
||||
/// 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: &str, max_size: usize) -> &str {
|
||||
if string.len() <= max_size {
|
||||
return string;
|
||||
}
|
||||
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 && (p[index] & 0b11000000) == 0b10000000 {
|
||||
index -= 1;
|
||||
}
|
||||
&string[0..index]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limit_string_test() {
|
||||
assert_eq!("hello", limit_string(&"hello world".to_owned(), 5));
|
||||
assert_eq!("hi ☹", limit_string(&"hi ☹☹".to_owned(), 6));
|
||||
assert_eq!("hi ", limit_string(&"hi ☹☹".to_owned(), 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_key_test() {
|
||||
assert_eq!("foo!", escape_key("foo!"));
|
||||
assert_eq!("foo{}", escape_key("foo{}"));
|
||||
assert_eq!("{}", escape_key("{}"));
|
||||
assert_eq!("", escape_key(""));
|
||||
assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb"));
|
||||
assert_eq!(
|
||||
"/path/to/foo&{}"@#.rb",
|
||||
escape_key("/path/to/foo&{}\"@#.rb")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,51 +1,19 @@
|
||||
mod extractor;
|
||||
mod trap;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate num_cpus;
|
||||
|
||||
use clap::arg;
|
||||
use flate2::write::GzEncoder;
|
||||
use encoding::{self};
|
||||
use rayon::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufWriter};
|
||||
use std::io::BufRead;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tree_sitter::{Language, Parser, Range};
|
||||
|
||||
enum TrapCompression {
|
||||
None,
|
||||
Gzip,
|
||||
}
|
||||
|
||||
impl TrapCompression {
|
||||
fn from_env() -> TrapCompression {
|
||||
match std::env::var("CODEQL_RUBY_TRAP_COMPRESSION") {
|
||||
Ok(method) => match TrapCompression::from_string(&method) {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
tracing::error!("Unknown compression method '{}'; using gzip.", &method);
|
||||
TrapCompression::Gzip
|
||||
}
|
||||
},
|
||||
// Default compression method if the env var isn't set:
|
||||
Err(_) => TrapCompression::Gzip,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_string(s: &str) -> Option<TrapCompression> {
|
||||
match s.to_lowercase().as_ref() {
|
||||
"none" => Some(TrapCompression::None),
|
||||
"gzip" => Some(TrapCompression::Gzip),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extension(&self) -> &str {
|
||||
match self {
|
||||
TrapCompression::None => "trap",
|
||||
TrapCompression::Gzip => "trap.gz",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of threads the extractor should use, by reading the
|
||||
* CODEQL_THREADS environment variable and using it as described in the
|
||||
@@ -75,6 +43,21 @@ fn num_codeql_threads() -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CP_NUMBER: regex::Regex = regex::Regex::new("cp([0-9]+)").unwrap();
|
||||
}
|
||||
|
||||
fn encoding_from_name(encoding_name: &str) -> Option<&(dyn encoding::Encoding + Send + Sync)> {
|
||||
match encoding::label::encoding_from_whatwg_label(encoding_name) {
|
||||
s @ Some(_) => s,
|
||||
None => CP_NUMBER.captures(encoding_name).and_then(|cap| {
|
||||
encoding::label::encoding_from_windows_code_page(
|
||||
str::parse(cap.get(1).unwrap().as_str()).unwrap(),
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_target(false)
|
||||
@@ -118,7 +101,7 @@ fn main() -> std::io::Result<()> {
|
||||
.value_of("output-dir")
|
||||
.expect("missing --output-dir");
|
||||
let trap_dir = PathBuf::from(trap_dir);
|
||||
let trap_compression = TrapCompression::from_env();
|
||||
let trap_compression = trap::Compression::from_env("CODEQL_RUBY_TRAP_COMPRESSION");
|
||||
|
||||
let file_list = matches.value_of("file-list").expect("missing --file-list");
|
||||
let file_list = fs::File::open(file_list)?;
|
||||
@@ -140,8 +123,9 @@ fn main() -> std::io::Result<()> {
|
||||
let path = PathBuf::from(line).canonicalize()?;
|
||||
let src_archive_file = path_for(&src_archive_dir, &path, "");
|
||||
let mut source = std::fs::read(&path)?;
|
||||
let mut needs_conversion = false;
|
||||
let code_ranges;
|
||||
let mut trap_writer = extractor::new_trap_writer();
|
||||
let mut trap_writer = trap::Writer::new();
|
||||
if path.extension().map_or(false, |x| x == "erb") {
|
||||
tracing::info!("scanning: {}", path.display());
|
||||
extractor::extract(
|
||||
@@ -168,6 +152,43 @@ fn main() -> std::io::Result<()> {
|
||||
}
|
||||
code_ranges = ranges;
|
||||
} else {
|
||||
if let Some(encoding_name) = scan_coding_comment(&source) {
|
||||
// If the input is already UTF-8 then there is no need to recode the source
|
||||
// If the declared encoding is 'binary' or 'ascii-8bit' then it is not clear how
|
||||
// to interpret characters. In this case it is probably best to leave the input
|
||||
// unchanged.
|
||||
if !encoding_name.eq_ignore_ascii_case("utf-8")
|
||||
&& !encoding_name.eq_ignore_ascii_case("ascii-8bit")
|
||||
&& !encoding_name.eq_ignore_ascii_case("binary")
|
||||
{
|
||||
if let Some(encoding) = encoding_from_name(&encoding_name) {
|
||||
needs_conversion =
|
||||
encoding.whatwg_name().unwrap_or_default() != "utf-8";
|
||||
if needs_conversion {
|
||||
match encoding
|
||||
.decode(&source, encoding::types::DecoderTrap::Replace)
|
||||
{
|
||||
Ok(str) => source = str.as_bytes().to_owned(),
|
||||
Err(msg) => {
|
||||
needs_conversion = false;
|
||||
tracing::warn!(
|
||||
"{}: character decoding failure: {} ({})",
|
||||
&path.to_string_lossy(),
|
||||
msg,
|
||||
&encoding_name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"{}: unknown character encoding: '{}'",
|
||||
&path.to_string_lossy(),
|
||||
&encoding_name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
code_ranges = vec![];
|
||||
}
|
||||
extractor::extract(
|
||||
@@ -180,34 +201,30 @@ fn main() -> std::io::Result<()> {
|
||||
&code_ranges,
|
||||
)?;
|
||||
std::fs::create_dir_all(&src_archive_file.parent().unwrap())?;
|
||||
std::fs::copy(&path, &src_archive_file)?;
|
||||
write_trap(&trap_dir, path, trap_writer, &trap_compression)
|
||||
if needs_conversion {
|
||||
std::fs::write(&src_archive_file, &source)?;
|
||||
} else {
|
||||
std::fs::copy(&path, &src_archive_file)?;
|
||||
}
|
||||
write_trap(&trap_dir, path, &trap_writer, trap_compression)
|
||||
})
|
||||
.expect("failed to extract files");
|
||||
|
||||
let path = PathBuf::from("extras");
|
||||
let mut trap_writer = extractor::new_trap_writer();
|
||||
trap_writer.populate_empty_location();
|
||||
write_trap(&trap_dir, path, trap_writer, &trap_compression)
|
||||
let mut trap_writer = trap::Writer::new();
|
||||
extractor::populate_empty_location(&mut trap_writer);
|
||||
write_trap(&trap_dir, path, &trap_writer, trap_compression)
|
||||
}
|
||||
|
||||
fn write_trap(
|
||||
trap_dir: &Path,
|
||||
path: PathBuf,
|
||||
trap_writer: extractor::TrapWriter,
|
||||
trap_compression: &TrapCompression,
|
||||
trap_writer: &trap::Writer,
|
||||
trap_compression: trap::Compression,
|
||||
) -> std::io::Result<()> {
|
||||
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);
|
||||
match trap_compression {
|
||||
TrapCompression::None => trap_writer.output(&mut trap_file),
|
||||
TrapCompression::Gzip => {
|
||||
let mut compressed_writer = GzEncoder::new(trap_file, flate2::Compression::fast());
|
||||
trap_writer.output(&mut compressed_writer)
|
||||
}
|
||||
}
|
||||
trap_writer.write_to_file(&trap_file, trap_compression)
|
||||
}
|
||||
|
||||
fn scan_erb(
|
||||
@@ -299,3 +316,143 @@ fn path_for(dir: &Path, path: &Path, ext: &str) -> PathBuf {
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn skip_space(content: &[u8], index: usize) -> usize {
|
||||
let mut index = index;
|
||||
while index < content.len() {
|
||||
let c = content[index] as char;
|
||||
// white space except \n
|
||||
let is_space = c == ' ' || ('\t'..='\r').contains(&c) && c != '\n';
|
||||
if !is_space {
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
fn scan_coding_comment(content: &[u8]) -> std::option::Option<Cow<str>> {
|
||||
let mut index = 0;
|
||||
// skip UTF-8 BOM marker if there is one
|
||||
if content.len() >= 3 && content[0] == 0xef && content[1] == 0xbb && content[2] == 0xbf {
|
||||
index += 3;
|
||||
}
|
||||
// skip #! line if there is one
|
||||
if index + 1 < content.len()
|
||||
&& content[index] as char == '#'
|
||||
&& content[index + 1] as char == '!'
|
||||
{
|
||||
index += 2;
|
||||
while index < content.len() && content[index] as char != '\n' {
|
||||
index += 1
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
index = skip_space(content, index);
|
||||
|
||||
if index >= content.len() || content[index] as char != '#' {
|
||||
return None;
|
||||
}
|
||||
index += 1;
|
||||
|
||||
const CODING: [char; 12] = ['C', 'c', 'O', 'o', 'D', 'd', 'I', 'i', 'N', 'n', 'G', 'g'];
|
||||
let mut word_index = 0;
|
||||
while index < content.len() && word_index < CODING.len() && content[index] as char != '\n' {
|
||||
if content[index] as char == CODING[word_index]
|
||||
|| content[index] as char == CODING[word_index + 1]
|
||||
{
|
||||
word_index += 2
|
||||
} else {
|
||||
word_index = 0;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
if word_index < CODING.len() {
|
||||
return None;
|
||||
}
|
||||
index = skip_space(content, index);
|
||||
|
||||
if index < content.len() && content[index] as char != ':' && content[index] as char != '=' {
|
||||
return None;
|
||||
}
|
||||
index += 1;
|
||||
index = skip_space(content, index);
|
||||
|
||||
let start = index;
|
||||
while index < content.len() {
|
||||
let c = content[index] as char;
|
||||
if c == '-' || c == '_' || c.is_ascii_alphanumeric() {
|
||||
index += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if index > start {
|
||||
return Some(String::from_utf8_lossy(&content[start..index]));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scan_coding_comment() {
|
||||
let text = "# encoding: utf-8";
|
||||
let result = scan_coding_comment(text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
let text = "#coding:utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
let text = "# foo\n# encoding: utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, None);
|
||||
|
||||
let text = "# encoding: latin1 encoding: utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("latin1".into()));
|
||||
|
||||
let text = "# encoding: nonsense";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("nonsense".into()));
|
||||
|
||||
let text = "# coding = utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
let text = "# CODING = utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
let text = "# CoDiNg = utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
let text = "# blah blahblahcoding = utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
// unicode BOM is ignored
|
||||
let text = "\u{FEFF}# encoding: utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
let text = "\u{FEFF} # encoding: utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
let text = "#! /usr/bin/env ruby\n # encoding: utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
let text = "\u{FEFF}#! /usr/bin/env ruby\n # encoding: utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
|
||||
// A #! must be the first thing on a line, otherwise it's a normal comment
|
||||
let text = " #! /usr/bin/env ruby encoding = utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, Some("utf-8".into()));
|
||||
let text = " #! /usr/bin/env ruby \n # encoding = utf-8";
|
||||
let result = scan_coding_comment(&text.as_bytes());
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
275
ruby/extractor/src/trap.rs
Normal file
275
ruby/extractor/src/trap.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use flate2::write::GzEncoder;
|
||||
|
||||
pub struct Writer {
|
||||
/// The accumulated trap entries
|
||||
trap_output: Vec<Entry>,
|
||||
/// A counter for generating fresh labels
|
||||
counter: u32,
|
||||
/// cache of global keys
|
||||
global_keys: std::collections::HashMap<String, Label>,
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
pub fn new() -> Writer {
|
||||
Writer {
|
||||
counter: 0,
|
||||
trap_output: Vec::new(),
|
||||
global_keys: std::collections::HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fresh_id(&mut self) -> Label {
|
||||
let label = Label(self.counter);
|
||||
self.counter += 1;
|
||||
self.trap_output.push(Entry::FreshId(label));
|
||||
label
|
||||
}
|
||||
|
||||
/// Gets a label that will hold the unique ID of the passed string at import time.
|
||||
/// This can be used for incrementally importable TRAP files -- use globally unique
|
||||
/// strings to compute a unique ID for table tuples.
|
||||
///
|
||||
/// Note: You probably want to make sure that the key strings that you use are disjoint
|
||||
/// for disjoint column types; the standard way of doing this is to prefix (or append)
|
||||
/// the column type name to the ID. Thus, you might identify methods in Java by the
|
||||
/// full ID "methods_com.method.package.DeclaringClass.method(argumentList)".
|
||||
pub fn global_id(&mut self, key: &str) -> (Label, bool) {
|
||||
if let Some(label) = self.global_keys.get(key) {
|
||||
return (*label, false);
|
||||
}
|
||||
let label = Label(self.counter);
|
||||
self.counter += 1;
|
||||
self.global_keys.insert(key.to_owned(), label);
|
||||
self.trap_output
|
||||
.push(Entry::MapLabelToKey(label, key.to_owned()));
|
||||
(label, true)
|
||||
}
|
||||
|
||||
pub fn add_tuple(&mut self, table_name: &str, args: Vec<Arg>) {
|
||||
self.trap_output
|
||||
.push(Entry::GenericTuple(table_name.to_owned(), args))
|
||||
}
|
||||
|
||||
pub fn comment(&mut self, text: String) {
|
||||
self.trap_output.push(Entry::Comment(text));
|
||||
}
|
||||
|
||||
pub fn write_to_file(&self, path: &Path, compression: Compression) -> std::io::Result<()> {
|
||||
let trap_file = std::fs::File::create(path)?;
|
||||
match compression {
|
||||
Compression::None => {
|
||||
let mut trap_file = BufWriter::new(trap_file);
|
||||
self.write_trap_entries(&mut trap_file)
|
||||
}
|
||||
Compression::Gzip => {
|
||||
let trap_file = GzEncoder::new(trap_file, flate2::Compression::fast());
|
||||
let mut trap_file = BufWriter::new(trap_file);
|
||||
self.write_trap_entries(&mut trap_file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_trap_entries<W: Write>(&self, file: &mut W) -> std::io::Result<()> {
|
||||
for trap_entry in &self.trap_output {
|
||||
writeln!(file, "{}", trap_entry)?;
|
||||
}
|
||||
std::io::Result::Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Entry {
|
||||
/// Maps the label to a fresh id, e.g. `#123=*`.
|
||||
FreshId(Label),
|
||||
/// Maps the label to a key, e.g. `#7=@"foo"`.
|
||||
MapLabelToKey(Label, String),
|
||||
/// foo_bar(arg*)
|
||||
GenericTuple(String, Vec<Arg>),
|
||||
Comment(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Entry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Entry::FreshId(label) => write!(f, "{}=*", label),
|
||||
Entry::MapLabelToKey(label, key) => {
|
||||
write!(f, "{}=@\"{}\"", label, key.replace("\"", "\"\""))
|
||||
}
|
||||
Entry::GenericTuple(name, args) => {
|
||||
write!(f, "{}(", name)?;
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ",")?;
|
||||
}
|
||||
write!(f, "{}", arg)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
Entry::Comment(line) => write!(f, "// {}", line),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
// Identifiers of the form #0, #1...
|
||||
pub struct Label(u32);
|
||||
|
||||
impl fmt::Display for Label {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "#{:x}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Some untyped argument to a TrapEntry.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Arg {
|
||||
Label(Label),
|
||||
Int(usize),
|
||||
String(String),
|
||||
}
|
||||
|
||||
const MAX_STRLEN: usize = 1048576;
|
||||
|
||||
impl fmt::Display for Arg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Arg::Label(x) => write!(f, "{}", x),
|
||||
Arg::Int(x) => write!(f, "{}", x),
|
||||
Arg::String(x) => write!(
|
||||
f,
|
||||
"\"{}\"",
|
||||
limit_string(x, MAX_STRLEN).replace("\"", "\"\"")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Program(Vec<Entry>);
|
||||
|
||||
impl fmt::Display for Program {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut text = String::new();
|
||||
for trap_entry in &self.0 {
|
||||
text.push_str(&format!("{}\n", trap_entry));
|
||||
}
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn full_id_for_file(normalized_path: &str) -> String {
|
||||
format!("{};sourcefile", escape_key(normalized_path))
|
||||
}
|
||||
|
||||
pub fn full_id_for_folder(normalized_path: &str) -> String {
|
||||
format!("{};folder", escape_key(normalized_path))
|
||||
}
|
||||
|
||||
/// Escapes a string for use in a TRAP key, by replacing special characters with
|
||||
/// HTML entities.
|
||||
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
|
||||
fn needs_escaping(c: char) -> bool {
|
||||
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
|
||||
}
|
||||
|
||||
let key = key.into();
|
||||
if key.contains(needs_escaping) {
|
||||
let mut escaped = String::with_capacity(2 * key.len());
|
||||
for c in key.chars() {
|
||||
match c {
|
||||
'&' => escaped.push_str("&"),
|
||||
'{' => escaped.push_str("{"),
|
||||
'}' => escaped.push_str("}"),
|
||||
'"' => escaped.push_str("""),
|
||||
'@' => escaped.push_str("@"),
|
||||
'#' => escaped.push_str("#"),
|
||||
_ => escaped.push(c),
|
||||
}
|
||||
}
|
||||
Cow::Owned(escaped)
|
||||
} else {
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
/// Limit the length (in bytes) of a string. If the string's length in bytes is
|
||||
/// less than or equal to the limit then the entire string is returned. Otherwise
|
||||
/// 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: &str, max_size: usize) -> &str {
|
||||
if string.len() <= max_size {
|
||||
return string;
|
||||
}
|
||||
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 && (p[index] & 0b11000000) == 0b10000000 {
|
||||
index -= 1;
|
||||
}
|
||||
&string[0..index]
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Compression {
|
||||
None,
|
||||
Gzip,
|
||||
}
|
||||
|
||||
impl Compression {
|
||||
pub fn from_env(var_name: &str) -> Compression {
|
||||
match std::env::var(var_name) {
|
||||
Ok(method) => match Compression::from_string(&method) {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
tracing::error!("Unknown compression method '{}'; using gzip.", &method);
|
||||
Compression::Gzip
|
||||
}
|
||||
},
|
||||
// Default compression method if the env var isn't set:
|
||||
Err(_) => Compression::Gzip,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(s: &str) -> Option<Compression> {
|
||||
match s.to_lowercase().as_ref() {
|
||||
"none" => Some(Compression::None),
|
||||
"gzip" => Some(Compression::Gzip),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extension(&self) -> &str {
|
||||
match self {
|
||||
Compression::None => "trap",
|
||||
Compression::Gzip => "trap.gz",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limit_string_test() {
|
||||
assert_eq!("hello", limit_string(&"hello world".to_owned(), 5));
|
||||
assert_eq!("hi ☹", limit_string(&"hi ☹☹".to_owned(), 6));
|
||||
assert_eq!("hi ", limit_string(&"hi ☹☹".to_owned(), 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_key_test() {
|
||||
assert_eq!("foo!", escape_key("foo!"));
|
||||
assert_eq!("foo{}", escape_key("foo{}"));
|
||||
assert_eq!("{}", escape_key("{}"));
|
||||
assert_eq!("", escape_key(""));
|
||||
assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb"));
|
||||
assert_eq!(
|
||||
"/path/to/foo&{}"@#.rb",
|
||||
escape_key("/path/to/foo&{}\"@#.rb")
|
||||
);
|
||||
}
|
||||
@@ -12,4 +12,4 @@ node-types = { path = "../node-types" }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
||||
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "6334d6ab3d04a5672da695d3b155ca3301511f8d" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "e75d04404c9dd71ad68850d5c672b226d5e694f3" }
|
||||
|
||||
@@ -10,6 +10,6 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
or
|
||||
n instanceof SummaryNode
|
||||
or
|
||||
n instanceof HashSplatArgumentsNode
|
||||
n instanceof SynthHashSplatArgumentNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
## 0.3.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Calls to `Arel.sql` are now recognised as propagating taint from their argument.
|
||||
* Calls to `ActiveRecord::Relation#annotate` are now recognized as `SqlExecution`s so that it will be considered as a sink for queries like rb/sql-injection.
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Fixed a bug causing every expression in the database to be considered a system-command execution sink when calls to any of the following methods exist:
|
||||
* The `spawn`, `fspawn`, `popen4`, `pspawn`, `system`, `_pspawn` methods and the backtick operator from the `POSIX::spawn` gem.
|
||||
* The `execute_command`, `rake`, `rails_command`, and `git` methods in `Rails::Generation::Actions`.
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `BarrierGuard` class has been deprecated. Such barriers and sanitizers can now instead be created using the new `BarrierGuard` parameterized module.
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- Calls to `Zip::File.open` and `Zip::File.new` have been added as `FileSystemAccess` sinks. As a result queries like `rb/path-injection` now flag up cases where users may access arbitrary archive files.
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Calls to `ActiveRecord::Base.create` and `ActiveRecord::Base.update` are now
|
||||
recognised as write accesses.
|
||||
5
ruby/ql/lib/change-notes/2022-08-04-mime-type.md
Normal file
5
ruby/ql/lib/change-notes/2022-08-04-mime-type.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Arguments to `Mime::Type#match?` and `Mime::Type#=~` are now recognised as
|
||||
regular expression sources.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
## 0.3.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `BarrierGuard` class has been deprecated. Such barriers and sanitizers can now instead be created using the new `BarrierGuard` parameterized module.
|
||||
8
ruby/ql/lib/change-notes/released/0.3.1.md
Normal file
8
ruby/ql/lib/change-notes/released/0.3.1.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## 0.3.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Fixed a bug causing every expression in the database to be considered a system-command execution sink when calls to any of the following methods exist:
|
||||
* The `spawn`, `fspawn`, `popen4`, `pspawn`, `system`, `_pspawn` methods and the backtick operator from the `POSIX::spawn` gem.
|
||||
* The `execute_command`, `rake`, `rails_command`, and `git` methods in `Rails::Generation::Actions`.
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
6
ruby/ql/lib/change-notes/released/0.3.2.md
Normal file
6
ruby/ql/lib/change-notes/released/0.3.2.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 0.3.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Calls to `Arel.sql` are now recognised as propagating taint from their argument.
|
||||
* Calls to `ActiveRecord::Relation#annotate` are now recognized as `SqlExecution`s so that it will be considered as a sink for queries like rb/sql-injection.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.3
|
||||
lastReleaseVersion: 0.3.2
|
||||
|
||||
@@ -19,48 +19,154 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatc
|
||||
*/
|
||||
module API {
|
||||
/**
|
||||
* An abstract representation of a definition or use of an API component such as a Ruby module,
|
||||
* or the result of a method call.
|
||||
* A node in the API graph, representing a value that has crossed the boundary between this
|
||||
* codebase and an external library (or in general, any external codebase).
|
||||
*
|
||||
* ### Basic usage
|
||||
*
|
||||
* API graphs are typically used to identify "API calls", that is, calls to an external function
|
||||
* whose implementation is not necessarily part of the current codebase.
|
||||
*
|
||||
* The most basic use of API graphs is typically as follows:
|
||||
* 1. Start with `API::getTopLevelMember` for the relevant library.
|
||||
* 2. Follow up with a chain of accessors such as `getMethod` describing how to get to the relevant API function.
|
||||
* 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`.
|
||||
*
|
||||
* For example, a simplified way to get arguments to `Foo.bar` would be
|
||||
* ```codeql
|
||||
* API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).asSink()
|
||||
* ```
|
||||
*
|
||||
* The most commonly used accessors are `getMember`, `getMethod`, `getParameter`, and `getReturn`.
|
||||
*
|
||||
* ### API graph nodes
|
||||
*
|
||||
* There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value:
|
||||
* - **Use-nodes** represent values held by the current codebase, which came from an external library.
|
||||
* (The current codebase is "using" a value that came from the library).
|
||||
* - **Def-nodes** represent values held by the external library, which came from this codebase.
|
||||
* (The current codebase "defines" the value seen by the library).
|
||||
*
|
||||
* API graph nodes are associated with data-flow nodes in the current codebase.
|
||||
* (Since external libraries are not part of the database, there is no way to associate with concrete
|
||||
* data-flow nodes from the external library).
|
||||
* - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase,
|
||||
* such as the return value of a call to an external function.
|
||||
* - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase,
|
||||
* such as an argument passed in a call to an external function.
|
||||
*
|
||||
*
|
||||
* ### Access paths and edge labels
|
||||
*
|
||||
* Nodes in the API graph are associated with a set of access paths, describing a series of operations
|
||||
* that may be performed to obtain that value.
|
||||
*
|
||||
* For example, the access path `API::getTopLevelMember("Foo").getMethod("bar")` represents the action of
|
||||
* reading the top-level constant `Foo` and then accessing the method `bar` on the resulting object.
|
||||
* It would be associated with a call such as `Foo.bar()`.
|
||||
*
|
||||
* Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node
|
||||
* determines who is performing the operation, and the type of the `B` node determines who ends up holding
|
||||
* the result:
|
||||
* - An edge starting from a use-node describes what the current codebase is doing to a value that
|
||||
* came from a library.
|
||||
* - An edge starting from a def-node describes what the external library might do to a value that
|
||||
* came from the current codebase.
|
||||
* - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node).
|
||||
* - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is
|
||||
* the place where it was "last seen" in the current codebase before flowing out)
|
||||
*
|
||||
* Because the implementation of the external library is not visible, it is not known exactly what operations
|
||||
* it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would
|
||||
* lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform
|
||||
* those operations. (When constructing these edges, we assume the library is somewhat well-behaved).
|
||||
*
|
||||
* For example, given this snippet:
|
||||
* ```ruby
|
||||
* Foo.bar(->(x) { doSomething(x) })
|
||||
* ```
|
||||
* A callback is passed to the external function `Foo.bar`. We can't know if `Foo.bar` will actually invoke this callback.
|
||||
* But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter.
|
||||
* For that reason, an edge is generated representing the argument-passing operation that might be performed by `Foo.bar`.
|
||||
* This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x` of the lambda.
|
||||
*/
|
||||
class Node extends Impl::TApiNode {
|
||||
/**
|
||||
* Gets a data-flow node corresponding to a use of the API component represented by this node.
|
||||
* Gets a data-flow node where this value may flow after entering the current codebase.
|
||||
*
|
||||
* For example, `Kernel.format "%s world!", "Hello"` is a use of the return of the `format` function of
|
||||
* the `Kernel` module.
|
||||
*
|
||||
* This includes indirect uses found via data flow.
|
||||
* This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow.
|
||||
* See `asSource()` for examples.
|
||||
*/
|
||||
DataFlow::Node getAUse() {
|
||||
DataFlow::Node getAValueReachableFromSource() {
|
||||
exists(DataFlow::LocalSourceNode src | Impl::use(this, src) |
|
||||
Impl::trackUseNode(src).flowsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immediate use of the API component represented by this node.
|
||||
* Gets a data-flow node where this value enters the current codebase.
|
||||
*
|
||||
* Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses
|
||||
* found via data flow.
|
||||
* For example:
|
||||
* ```ruby
|
||||
* # API::getTopLevelMember("Foo").asSource()
|
||||
* Foo
|
||||
*
|
||||
* # API::getTopLevelMember("Foo").getMethod("bar").getReturn().asSource()
|
||||
* Foo.bar
|
||||
*
|
||||
* # 'x' is found by:
|
||||
* # API::getTopLevelMember("Foo").getMethod("bar").getBlock().getParameter(0).asSource()
|
||||
* Foo.bar do |x|
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) }
|
||||
DataFlow::LocalSourceNode asSource() { Impl::use(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node corresponding the value flowing into this API component.
|
||||
* Gets a data-flow node where this value leaves the current codebase and flows into an
|
||||
* external library (or in general, any external codebase).
|
||||
*
|
||||
* Concretely, this corresponds to an argument passed to a call to external code.
|
||||
*
|
||||
* For example:
|
||||
* ```ruby
|
||||
* # 'x' is found by:
|
||||
* # API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).asSink()
|
||||
* Foo.bar(x)
|
||||
*
|
||||
* Foo.bar(-> {
|
||||
* # 'x' is found by:
|
||||
* # API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).getReturn().asSink()
|
||||
* x
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
DataFlow::Node getARhs() { Impl::def(this, result) }
|
||||
DataFlow::Node asSink() { Impl::def(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that may interprocedurally flow to the value escaping into this API component.
|
||||
* Get a data-flow node that transitively flows to an external library (or in general, any external codebase).
|
||||
*
|
||||
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
|
||||
* See `asSink()` for examples.
|
||||
*/
|
||||
DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) }
|
||||
DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource()`. */
|
||||
deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSource()`. */
|
||||
deprecated DataFlow::LocalSourceNode getAnImmediateUse() { result = this.asSource() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSink()`. */
|
||||
deprecated DataFlow::Node getARhs() { result = this.asSink() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachingSink()`. */
|
||||
deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() }
|
||||
|
||||
/**
|
||||
* Gets a call to a method on the receiver represented by this API component.
|
||||
*/
|
||||
DataFlow::CallNode getAMethodCall(string method) {
|
||||
result = this.getReturn(method).getAnImmediateUse()
|
||||
}
|
||||
DataFlow::CallNode getAMethodCall(string method) { result = this.getReturn(method).asSource() }
|
||||
|
||||
/**
|
||||
* Gets a node representing member `m` of this API component.
|
||||
@@ -135,7 +241,7 @@ module API {
|
||||
/**
|
||||
* Gets a `new` call to the function represented by this API component.
|
||||
*/
|
||||
DataFlow::ExprNode getAnInstantiation() { result = this.getInstance().getAnImmediateUse() }
|
||||
DataFlow::ExprNode getAnInstantiation() { result = this.getInstance().asSource() }
|
||||
|
||||
/**
|
||||
* Gets a node representing a (direct or indirect) subclass of the class represented by this node.
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
*/
|
||||
|
||||
private import codeql.ruby.frameworks.Core
|
||||
private import codeql.ruby.frameworks.ActionCable
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.ActiveSupport
|
||||
private import codeql.ruby.frameworks.Archive
|
||||
private import codeql.ruby.frameworks.Arel
|
||||
private import codeql.ruby.frameworks.GraphQL
|
||||
private import codeql.ruby.frameworks.Rails
|
||||
private import codeql.ruby.frameworks.Railties
|
||||
private import codeql.ruby.frameworks.Stdlib
|
||||
private import codeql.ruby.frameworks.Files
|
||||
private import codeql.ruby.frameworks.HttpClients
|
||||
|
||||
@@ -92,20 +92,32 @@ private class ParsedStringRegExp extends RegExp {
|
||||
override string getFlags() { none() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling regular expression interpretations. */
|
||||
module RegExpInterpretation {
|
||||
/**
|
||||
* A node that is not a regular expression literal, but is used in places that
|
||||
* may interpret it as one. Instances of this class are typically strings that
|
||||
* flow to method calls like `RegExp.new`.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `source` may be interpreted as a regular expression.
|
||||
* A node interpreted as a regular expression.
|
||||
*/
|
||||
private predicate isInterpretedAsRegExp(DataFlow::Node source) {
|
||||
// The first argument to an invocation of `Regexp.new` or `Regexp.compile`.
|
||||
source = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]).getArgument(0)
|
||||
or
|
||||
// The argument of a call that coerces the argument to a regular expression.
|
||||
exists(DataFlow::CallNode mce |
|
||||
mce.getMethodName() = ["match", "match?"] and
|
||||
source = mce.getArgument(0) and
|
||||
// exclude https://ruby-doc.org/core-2.4.0/Regexp.html#method-i-match
|
||||
not mce.getReceiver().asExpr().getExpr() instanceof AST::RegExpLiteral
|
||||
)
|
||||
class StdLibRegExpInterpretation extends RegExpInterpretation::Range {
|
||||
StdLibRegExpInterpretation() {
|
||||
// The first argument to an invocation of `Regexp.new` or `Regexp.compile`.
|
||||
this = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]).getArgument(0)
|
||||
or
|
||||
// The argument of a call that coerces the argument to a regular expression.
|
||||
exists(DataFlow::CallNode mce |
|
||||
mce.getMethodName() = ["match", "match?"] and
|
||||
this = mce.getArgument(0) and
|
||||
// exclude https://ruby-doc.org/core-2.4.0/Regexp.html#method-i-match
|
||||
not mce.getReceiver().asExpr().getExpr() instanceof AST::RegExpLiteral
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class RegExpConfiguration extends Configuration {
|
||||
@@ -120,7 +132,7 @@ private class RegExpConfiguration extends Configuration {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { isInterpretedAsRegExp(sink) }
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof RegExpInterpretation::Range }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
// stop flow if `node` is receiver of
|
||||
|
||||
@@ -36,7 +36,7 @@ private import ExprNodes
|
||||
* constant value in some cases.
|
||||
*/
|
||||
private module Propagation {
|
||||
private ExprCfgNode getSource(VariableReadAccessCfgNode read) {
|
||||
ExprCfgNode getSource(VariableReadAccessCfgNode read) {
|
||||
exists(Ssa::WriteDefinition def |
|
||||
def.assigns(result) and
|
||||
read = def.getARead()
|
||||
@@ -509,3 +509,53 @@ private module Cached {
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
/**
|
||||
* Holds if the control flow node `e` refers to an array constructed from the
|
||||
* array literal `arr`.
|
||||
* Example:
|
||||
* ```rb
|
||||
* [1, 2, 3]
|
||||
* C = [1, 2, 3]; C
|
||||
* x = [1, 2, 3]; x
|
||||
* ```
|
||||
*/
|
||||
predicate isArrayConstant(ExprCfgNode e, ArrayLiteralCfgNode arr) {
|
||||
// [...]
|
||||
e = arr
|
||||
or
|
||||
// e = [...]; e
|
||||
isArrayConstant(getSource(e), arr)
|
||||
or
|
||||
isArrayExpr(e.getExpr(), arr)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the expression `e` refers to an array constructed from the array literal `arr`.
|
||||
*/
|
||||
private predicate isArrayExpr(Expr e, ArrayLiteralCfgNode arr) {
|
||||
// e = [...]
|
||||
e = arr.getExpr()
|
||||
or
|
||||
// Like above, but handles the desugaring of array literals to Array.[] calls.
|
||||
e.getDesugared() = arr.getExpr()
|
||||
or
|
||||
// A = [...]; A
|
||||
// A = a; A
|
||||
isArrayExpr(e.(ConstantReadAccess).getValue(), arr)
|
||||
or
|
||||
// Recurse via CFG nodes. Necessary for example in:
|
||||
// a = [...]
|
||||
// A = a
|
||||
// A
|
||||
//
|
||||
// We map from A to a via ConstantReadAccess::getValue, yielding the Expr a.
|
||||
// To get to [...] we need to go via getSource(ExprCfgNode e), so we find a
|
||||
// CFG node for a and call `isArrayConstant`.
|
||||
//
|
||||
// The use of `forex` is intended to ensure that a is an array constant in all
|
||||
// control flow paths.
|
||||
// Note(hmac): I don't think this is necessary, as `getSource` will not return
|
||||
// results if the source is a phi node.
|
||||
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isArrayConstant(n, arr))
|
||||
}
|
||||
|
||||
@@ -357,6 +357,14 @@ module ExprNodes {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `n`th positional argument of this call.
|
||||
* Unlike `getArgument`, this excludes keyword arguments.
|
||||
*/
|
||||
final ExprCfgNode getPositionalArgument(int n) {
|
||||
result = this.getArgument(n) and not result instanceof PairCfgNode
|
||||
}
|
||||
|
||||
/** Gets the number of arguments of this call. */
|
||||
final int getNumberOfArguments() { result = e.getNumberOfArguments() }
|
||||
|
||||
@@ -374,6 +382,9 @@ module ExprNodes {
|
||||
MethodCallCfgNode() { super.getExpr() instanceof MethodCall }
|
||||
|
||||
override MethodCall getExpr() { result = super.getExpr() }
|
||||
|
||||
/** Gets the name of this method call. */
|
||||
string getMethodName() { result = this.getExpr().getMethodName() }
|
||||
}
|
||||
|
||||
private class CaseExprChildMapping extends ExprChildMapping, CaseExpr {
|
||||
|
||||
@@ -881,7 +881,12 @@ import Cached
|
||||
* graph is restricted to nodes from `RelevantNode`.
|
||||
*/
|
||||
module TestOutput {
|
||||
abstract class RelevantNode extends Node { }
|
||||
abstract class RelevantNode extends Node {
|
||||
/**
|
||||
* Gets a string used to resolve ties in node and edge ordering.
|
||||
*/
|
||||
string getOrderDisambuigation() { result = "" }
|
||||
}
|
||||
|
||||
query predicate nodes(RelevantNode n, string attr, string val) {
|
||||
attr = "semmle.order" and
|
||||
@@ -894,7 +899,8 @@ module TestOutput {
|
||||
p
|
||||
order by
|
||||
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
|
||||
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), p.toString()
|
||||
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), p.toString(),
|
||||
p.getOrderDisambuigation()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
@@ -916,7 +922,8 @@ module TestOutput {
|
||||
s
|
||||
order by
|
||||
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
|
||||
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), t.toString()
|
||||
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), t.toString(), s.toString(),
|
||||
s.getOrderDisambuigation()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
private import codeql.ruby.ast.internal.Constant
|
||||
private import codeql.ruby.InclusionTests
|
||||
|
||||
private predicate stringConstCompare(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
|
||||
exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c |
|
||||
@@ -61,19 +65,7 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard,
|
||||
// The value of the condition that results in the node being validated.
|
||||
private boolean checkedBranch;
|
||||
|
||||
StringConstCompare() {
|
||||
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
|
||||
this.getExpr() instanceof EqExpr and checkedBranch = true
|
||||
or
|
||||
this.getExpr() instanceof CaseEqExpr and checkedBranch = true
|
||||
or
|
||||
this.getExpr() instanceof NEExpr and checkedBranch = false
|
||||
|
|
||||
this.getLeftOperand() = strLitNode and this.getRightOperand() = checkedNode
|
||||
or
|
||||
this.getLeftOperand() = checkedNode and this.getRightOperand() = strLitNode
|
||||
)
|
||||
}
|
||||
StringConstCompare() { stringConstCompare(this, checkedNode, checkedBranch) }
|
||||
|
||||
override predicate checks(CfgNode expr, boolean branch) {
|
||||
expr = checkedNode and branch = checkedBranch
|
||||
@@ -81,15 +73,19 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard,
|
||||
}
|
||||
|
||||
private predicate stringConstArrayInclusionCall(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
|
||||
exists(CfgNodes::ExprNodes::MethodCallCfgNode mc, ArrayLiteral aLit |
|
||||
mc = g and
|
||||
mc.getExpr().getMethodName() = "include?" and
|
||||
[mc.getExpr().getReceiver(), mc.getExpr().getReceiver().(ConstantReadAccess).getValue()] = aLit
|
||||
exists(InclusionTest t |
|
||||
t.asExpr() = g and
|
||||
e = t.getContainedNode().asExpr() and
|
||||
branch = t.getPolarity()
|
||||
|
|
||||
forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and
|
||||
mc.getArgument(0) = e
|
||||
) and
|
||||
branch = true
|
||||
exists(ExprNodes::ArrayLiteralCfgNode arr |
|
||||
isArrayConstant(t.getContainerNode().asExpr(), arr)
|
||||
|
|
||||
forall(ExprCfgNode elem | elem = arr.getAnArgument() |
|
||||
elem instanceof ExprNodes::StringLiteralCfgNode
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,16 +128,7 @@ deprecated class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
|
||||
CfgNodes::ExprNodes::MethodCallCfgNode {
|
||||
private CfgNode checkedNode;
|
||||
|
||||
StringConstArrayInclusionCall() {
|
||||
exists(ArrayLiteral aLit |
|
||||
this.getExpr().getMethodName() = "include?" and
|
||||
[this.getExpr().getReceiver(), this.getExpr().getReceiver().(ConstantReadAccess).getValue()] =
|
||||
aLit
|
||||
|
|
||||
forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and
|
||||
this.getArgument(0) = checkedNode
|
||||
)
|
||||
}
|
||||
StringConstArrayInclusionCall() { stringConstArrayInclusionCall(this, checkedNode, true) }
|
||||
|
||||
override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true }
|
||||
}
|
||||
|
||||
@@ -65,7 +65,12 @@ class DataFlowCallable extends TDataFlowCallable {
|
||||
string toString() { result = [this.asCallable().toString(), this.asLibraryCallable()] }
|
||||
|
||||
/** Gets the location of this callable. */
|
||||
Location getLocation() { result = this.asCallable().getLocation() }
|
||||
Location getLocation() {
|
||||
result = this.asCallable().getLocation()
|
||||
or
|
||||
this instanceof TLibraryCallable and
|
||||
result instanceof EmptyLocation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
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
@@ -227,6 +227,9 @@ private module Cached {
|
||||
} or
|
||||
TSelfParameterNode(MethodBase m) or
|
||||
TBlockParameterNode(MethodBase m) or
|
||||
TSynthHashSplatParameterNode(DataFlowCallable c) {
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_)))
|
||||
} or
|
||||
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) {
|
||||
n instanceof Argument or
|
||||
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
|
||||
@@ -240,12 +243,13 @@ private module Cached {
|
||||
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
|
||||
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
|
||||
} or
|
||||
THashSplatArgumentsNode(CfgNodes::ExprNodes::CallCfgNode c) {
|
||||
TSynthHashSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
|
||||
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
|
||||
}
|
||||
|
||||
class TParameterNode =
|
||||
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode;
|
||||
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
|
||||
TSynthHashSplatParameterNode or TSummaryParameterNode;
|
||||
|
||||
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
|
||||
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
|
||||
@@ -328,18 +332,21 @@ private module Cached {
|
||||
|
||||
cached
|
||||
predicate isLocalSourceNode(Node n) {
|
||||
n instanceof ParameterNode
|
||||
or
|
||||
n instanceof PostUpdateNodes::ExprPostUpdateNode
|
||||
or
|
||||
// Nodes that can't be reached from another entry definition or expression.
|
||||
not reachedFromExprOrEntrySsaDef(n)
|
||||
or
|
||||
// Ensure all entry SSA definitions are local sources -- for parameters, this
|
||||
// is needed by type tracking. Note that when the parameter has a default value,
|
||||
// it will be reachable from an expression (the default value) and therefore
|
||||
// won't be caught by the rule above.
|
||||
entrySsaDefinition(n)
|
||||
not n instanceof SynthHashSplatParameterNode and
|
||||
(
|
||||
n instanceof ParameterNode
|
||||
or
|
||||
n instanceof PostUpdateNodes::ExprPostUpdateNode
|
||||
or
|
||||
// Nodes that can't be reached from another entry definition or expression.
|
||||
not reachedFromExprOrEntrySsaDef(n)
|
||||
or
|
||||
// Ensure all entry SSA definitions are local sources -- for parameters, this
|
||||
// is needed by type tracking. Note that when the parameter has a default value,
|
||||
// it will be reachable from an expression (the default value) and therefore
|
||||
// won't be caught by the rule above.
|
||||
entrySsaDefinition(n)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
@@ -415,7 +422,9 @@ predicate nodeIsHidden(Node n) {
|
||||
or
|
||||
n instanceof SynthReturnNode
|
||||
or
|
||||
n instanceof HashSplatArgumentsNode
|
||||
n instanceof SynthHashSplatParameterNode
|
||||
or
|
||||
n instanceof SynthHashSplatArgumentNode
|
||||
}
|
||||
|
||||
/** An SSA definition, viewed as a node in a data flow graph. */
|
||||
@@ -470,10 +479,13 @@ private module ParameterNodes {
|
||||
abstract class ParameterNodeImpl extends NodeImpl {
|
||||
abstract Parameter getParameter();
|
||||
|
||||
abstract predicate isSourceParameterOf(Callable c, ParameterPosition pos);
|
||||
abstract predicate isParameterOf(DataFlowCallable c, ParameterPosition pos);
|
||||
|
||||
predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
this.isSourceParameterOf(c.asCallable(), pos)
|
||||
final predicate isSourceParameterOf(Callable c, ParameterPosition pos) {
|
||||
exists(DataFlowCallable callable |
|
||||
this.isParameterOf(callable, pos) and
|
||||
c = callable.asCallable()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,21 +500,23 @@ private module ParameterNodes {
|
||||
|
||||
override Parameter getParameter() { result = parameter }
|
||||
|
||||
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) {
|
||||
exists(int i | pos.isPositional(i) and c.getParameter(i) = parameter |
|
||||
parameter instanceof SimpleParameter
|
||||
or
|
||||
parameter instanceof OptionalParameter
|
||||
)
|
||||
or
|
||||
parameter =
|
||||
any(KeywordParameter kp |
|
||||
c.getAParameter() = kp and
|
||||
pos.isKeyword(kp.getName())
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
exists(Callable callable | callable = c.asCallable() |
|
||||
exists(int i | pos.isPositional(i) and callable.getParameter(i) = parameter |
|
||||
parameter instanceof SimpleParameter
|
||||
or
|
||||
parameter instanceof OptionalParameter
|
||||
)
|
||||
or
|
||||
parameter = c.getAParameter().(HashSplatParameter) and
|
||||
pos.isHashSplat()
|
||||
or
|
||||
parameter =
|
||||
any(KeywordParameter kp |
|
||||
callable.getAParameter() = kp and
|
||||
pos.isKeyword(kp.getName())
|
||||
)
|
||||
or
|
||||
parameter = callable.getAParameter().(HashSplatParameter) and
|
||||
pos.isHashSplat()
|
||||
)
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { result = parameter.getCallable() }
|
||||
@@ -525,8 +539,8 @@ private module ParameterNodes {
|
||||
|
||||
override Parameter getParameter() { none() }
|
||||
|
||||
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) {
|
||||
method = c and pos.isSelf()
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
method = c.asCallable() and pos.isSelf()
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { result = method }
|
||||
@@ -551,8 +565,8 @@ private module ParameterNodes {
|
||||
result = method.getAParameter() and result instanceof BlockParameter
|
||||
}
|
||||
|
||||
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) {
|
||||
c = method and pos.isBlock()
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
c.asCallable() = method and pos.isBlock()
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { result = method }
|
||||
@@ -570,6 +584,73 @@ private module ParameterNodes {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For all methods containing keyword parameters, we construct a synthesized
|
||||
* (hidden) parameter node to contain all keyword arguments. This allows us
|
||||
* to handle cases like
|
||||
*
|
||||
* ```rb
|
||||
* def foo(p1:, p2:)
|
||||
* sink(p1)
|
||||
* sink(p2)
|
||||
* end
|
||||
*
|
||||
* args = {:p1 => taint(1), :p2 => taint(2) }
|
||||
* foo(**args)
|
||||
* ```
|
||||
*
|
||||
* by adding read steps out of the synthesized parameter node to the relevant
|
||||
* keyword parameters.
|
||||
*
|
||||
* Note that this will introduce a bit of redundancy in cases like
|
||||
*
|
||||
* ```rb
|
||||
* foo(p1: taint(1), p2: taint(2))
|
||||
* ```
|
||||
*
|
||||
* where direct keyword matching is possible, since we construct a synthesized hash
|
||||
* splat argument (`SynthHashSplatArgumentNode`) at the call site, which means that
|
||||
* `taint(1)` will flow into `p1` both via normal keyword matching and via the synthesized
|
||||
* nodes (and similarly for `p2`). However, this redunancy is OK since
|
||||
* (a) it means that type-tracking through keyword arguments also works in most cases,
|
||||
* (b) read/store steps can be avoided when direct keyword matching is possible, and
|
||||
* hence access path limits are not a concern, and
|
||||
* (c) since the synthesized nodes are hidden, the reported data-flow paths will be
|
||||
* collapsed anyway.
|
||||
*/
|
||||
class SynthHashSplatParameterNode extends ParameterNodeImpl, TSynthHashSplatParameterNode {
|
||||
private DataFlowCallable callable;
|
||||
|
||||
SynthHashSplatParameterNode() { this = TSynthHashSplatParameterNode(callable) }
|
||||
|
||||
/**
|
||||
* Gets a keyword parameter that will be the result of reading `c` out of this
|
||||
* synthesized node.
|
||||
*/
|
||||
ParameterNode getAKeywordParameter(ContentSet c) {
|
||||
exists(string name |
|
||||
isParameterNode(result, callable, any(ParameterPosition p | p.isKeyword(name)))
|
||||
|
|
||||
c = getKeywordContent(name) or
|
||||
c.isSingleton(TUnknownElementContent())
|
||||
)
|
||||
}
|
||||
|
||||
final override Parameter getParameter() { none() }
|
||||
|
||||
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
c = callable and pos.isHashSplat()
|
||||
}
|
||||
|
||||
final override CfgScope getCfgScope() { result = callable.asCallable() }
|
||||
|
||||
final override DataFlowCallable getEnclosingCallable() { result = callable }
|
||||
|
||||
final override Location getLocationImpl() { result = callable.getLocation() }
|
||||
|
||||
final override string toStringImpl() { result = "**kwargs" }
|
||||
}
|
||||
|
||||
/** A parameter for a library callable with a flow summary. */
|
||||
class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable sc;
|
||||
@@ -579,8 +660,6 @@ private module ParameterNodes {
|
||||
|
||||
override Parameter getParameter() { none() }
|
||||
|
||||
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
sc = c.asLibraryCallable() and pos = pos_
|
||||
}
|
||||
@@ -689,10 +768,10 @@ private module ArgumentNodes {
|
||||
* part of the method signature, such that those cannot end up in the hash-splat
|
||||
* parameter.
|
||||
*/
|
||||
class HashSplatArgumentsNode extends ArgumentNode, THashSplatArgumentsNode {
|
||||
class SynthHashSplatArgumentNode extends ArgumentNode, TSynthHashSplatArgumentNode {
|
||||
CfgNodes::ExprNodes::CallCfgNode c;
|
||||
|
||||
HashSplatArgumentsNode() { this = THashSplatArgumentsNode(c) }
|
||||
SynthHashSplatArgumentNode() { this = TSynthHashSplatArgumentNode(c) }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this.sourceArgumentOf(call.asCall(), pos)
|
||||
@@ -704,10 +783,10 @@ private module ArgumentNodes {
|
||||
}
|
||||
}
|
||||
|
||||
private class HashSplatArgumentsNodeImpl extends NodeImpl, THashSplatArgumentsNode {
|
||||
private class SynthHashSplatArgumentNodeImpl extends NodeImpl, TSynthHashSplatArgumentNode {
|
||||
CfgNodes::ExprNodes::CallCfgNode c;
|
||||
|
||||
HashSplatArgumentsNodeImpl() { this = THashSplatArgumentsNode(c) }
|
||||
SynthHashSplatArgumentNodeImpl() { this = TSynthHashSplatArgumentNode(c) }
|
||||
|
||||
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
|
||||
|
||||
@@ -929,7 +1008,7 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
or
|
||||
// Wrap all keyword arguments in a synthesized hash-splat argument node
|
||||
exists(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition keywordPos, string name |
|
||||
node2 = THashSplatArgumentsNode(call) and
|
||||
node2 = TSynthHashSplatArgumentNode(call) and
|
||||
node1.asExpr().(Argument).isArgumentOf(call, keywordPos) and
|
||||
keywordPos.isKeyword(name) and
|
||||
c = getKeywordContent(name)
|
||||
@@ -962,6 +1041,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
))
|
||||
)
|
||||
or
|
||||
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,14 @@ class CallNode extends LocalSourceNode, ExprNode {
|
||||
/** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
|
||||
ExprNode getKeywordArgument(string name) { result.getExprNode() = node.getKeywordArgument(name) }
|
||||
|
||||
/**
|
||||
* Gets the `n`th positional argument of this call.
|
||||
* Unlike `getArgument`, this excludes keyword arguments.
|
||||
*/
|
||||
final ExprNode getPositionalArgument(int n) {
|
||||
result.getExprNode() = node.getPositionalArgument(n)
|
||||
}
|
||||
|
||||
/** Gets the name of the the method called by the method call (if any) corresponding to this data-flow node */
|
||||
string getMethodName() { result = node.getExpr().(MethodCall).getMethodName() }
|
||||
|
||||
|
||||
29
ruby/ql/lib/codeql/ruby/frameworks/ActionCable.qll
Normal file
29
ruby/ql/lib/codeql/ruby/frameworks/ActionCable.qll
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Modeling for `ActionCable`, which is a websocket gem that ships with Rails.
|
||||
* https://rubygems.org/gems/actioncable
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger
|
||||
|
||||
/**
|
||||
* Modeling for `ActionCable`.
|
||||
*/
|
||||
module ActionCable {
|
||||
/**
|
||||
* `ActionCable::Connection::TaggedLoggerProxy`
|
||||
*/
|
||||
module Logger {
|
||||
private class ActionCableLoggerInstantiation extends StdlibLogger::LoggerInstantiation {
|
||||
ActionCableLoggerInstantiation() {
|
||||
this =
|
||||
API::getTopLevelMember("ActionCable")
|
||||
.getMember("Connection")
|
||||
.getMember("TaggedLoggerProxy")
|
||||
.getAnInstantiation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class ActionControllerControllerClass extends ClassDeclaration {
|
||||
// In Rails applications `ApplicationController` typically extends `ActionController::Base`, but we
|
||||
// treat it separately in case the `ApplicationController` definition is not in the database.
|
||||
API::getTopLevelMember("ApplicationController")
|
||||
].getASubclass().getAUse().asExpr().getExpr()
|
||||
].getASubclass().getAValueReachableFromSource().asExpr().getExpr()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +83,7 @@ class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler:
|
||||
* Gets a route to this handler, if one exists.
|
||||
* May return multiple results.
|
||||
*/
|
||||
ActionDispatch::Route getARoute() {
|
||||
ActionDispatch::Routing::Route getARoute() {
|
||||
exists(string name |
|
||||
isRoute(result, name, controllerClass) and
|
||||
isActionControllerMethod(this, name, controllerClass)
|
||||
@@ -93,10 +93,10 @@ class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler:
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate isRoute(
|
||||
ActionDispatch::Route route, string name, ActionControllerControllerClass controllerClass
|
||||
ActionDispatch::Routing::Route route, string name, ActionControllerControllerClass controllerClass
|
||||
) {
|
||||
route.getController() + "_controller" =
|
||||
ActionDispatch::underscore(namespaceDeclaration(controllerClass)) and
|
||||
ActionDispatch::Routing::underscore(namespaceDeclaration(controllerClass)) and
|
||||
name = route.getAction()
|
||||
}
|
||||
|
||||
@@ -180,18 +180,31 @@ private class ActionControllerHtmlEscapeCall extends HtmlEscapeCall {
|
||||
* specific URL/path or to a different action in this controller.
|
||||
*/
|
||||
class RedirectToCall extends ActionControllerContextCall {
|
||||
RedirectToCall() { this.getMethodName() = "redirect_to" }
|
||||
RedirectToCall() {
|
||||
this.getMethodName() = ["redirect_to", "redirect_back", "redirect_back_or_to"]
|
||||
}
|
||||
|
||||
/** Gets the `Expr` representing the URL to redirect to, if any */
|
||||
Expr getRedirectUrl() { result = this.getArgument(0) }
|
||||
Expr getRedirectUrl() {
|
||||
this.getMethodName() = "redirect_back" and result = this.getKeywordArgument("fallback_location")
|
||||
or
|
||||
this.getMethodName() = ["redirect_to", "redirect_back_or_to"] and result = this.getArgument(0)
|
||||
}
|
||||
|
||||
/** Gets the `ActionControllerActionMethod` to redirect to, if any */
|
||||
ActionControllerActionMethod getRedirectActionMethod() {
|
||||
exists(string methodName |
|
||||
this.getKeywordArgument("action").getConstantValue().isStringlikeValue(methodName) and
|
||||
methodName = result.getName() and
|
||||
result.getEnclosingModule() = this.getControllerClass()
|
||||
)
|
||||
this.getKeywordArgument("action").getConstantValue().isStringlikeValue(result.getName()) and
|
||||
result.getEnclosingModule() = this.getControllerClass()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this method call allows a redirect to an external host.
|
||||
*/
|
||||
predicate allowsExternalRedirect() {
|
||||
// Unless the option allow_other_host is explicitly set to false, assume that external redirects are allowed.
|
||||
// TODO: Take into account `config.action_controller.raise_on_open_redirects`.
|
||||
// TODO: Take into account that this option defaults to false in Rails 7.
|
||||
not this.getKeywordArgument("allow_other_host").getConstantValue().isBoolean(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,7 @@ class ActiveRecordModelClass extends ClassDeclaration {
|
||||
// In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we
|
||||
// treat it separately in case the `ApplicationRecord` definition is not in the database.
|
||||
API::getTopLevelMember("ApplicationRecord")
|
||||
].getASubclass().getAUse().asExpr().getExpr()
|
||||
].getASubclass().getAValueReachableFromSource().asExpr().getExpr()
|
||||
}
|
||||
|
||||
// Gets the class declaration for this class and all of its super classes
|
||||
@@ -133,6 +133,11 @@ private Expr sqlFragmentArgument(MethodCall call) {
|
||||
or
|
||||
methodName = "reload" and
|
||||
result = call.getKeywordArgument("lock")
|
||||
or
|
||||
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
|
||||
// SQLi if user supplied input is passed in as an argument.
|
||||
methodName = "annotate" and
|
||||
result = call.getArgument(_)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -330,12 +335,13 @@ class ActiveRecordInstance extends DataFlow::Node {
|
||||
ActiveRecordModelClass getClass() { result = instantiation.getClass() }
|
||||
}
|
||||
|
||||
// A call whose receiver may be an active record model object
|
||||
private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
|
||||
/** A call whose receiver may be an active record model object */
|
||||
class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
|
||||
private ActiveRecordInstance instance;
|
||||
|
||||
ActiveRecordInstanceMethodCall() { this.getReceiver() = instance }
|
||||
|
||||
/** Gets the `ActiveRecordInstance` that is the receiver of this call. */
|
||||
ActiveRecordInstance getInstance() { result = instance }
|
||||
}
|
||||
|
||||
@@ -358,15 +364,6 @@ private module Persistence {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` has a keyword argument of with value `value`.
|
||||
*/
|
||||
private predicate keywordArgumentWithValue(DataFlow::CallNode call, DataFlow::ExprNode value) {
|
||||
exists(ExprNodes::PairCfgNode pair | pair = call.getArgument(_).asExpr() |
|
||||
value.asExpr() = pair.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
/** A call to e.g. `User.create(name: "foo")` */
|
||||
private class CreateLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
|
||||
CreateLikeCall() {
|
||||
@@ -380,8 +377,12 @@ private module Persistence {
|
||||
|
||||
override DataFlow::Node getValue() {
|
||||
// attrs as hash elements in arg0
|
||||
hashArgumentWithValue(this, 0, result) or
|
||||
keywordArgumentWithValue(this, result)
|
||||
hashArgumentWithValue(this, 0, result)
|
||||
or
|
||||
result = this.getKeywordArgument(_)
|
||||
or
|
||||
result = this.getPositionalArgument(0) and
|
||||
not result.asExpr() instanceof ExprNodes::HashLiteralCfgNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,11 +394,19 @@ private module Persistence {
|
||||
}
|
||||
|
||||
override DataFlow::Node getValue() {
|
||||
keywordArgumentWithValue(this, result)
|
||||
// User.update(1, name: "foo")
|
||||
result = this.getKeywordArgument(_)
|
||||
or
|
||||
// User.update(1, params)
|
||||
exists(int n | n > 0 |
|
||||
result = this.getPositionalArgument(n) and
|
||||
not result.asExpr() instanceof ExprNodes::ArrayLiteralCfgNode
|
||||
)
|
||||
or
|
||||
// Case where 2 array args are passed - the first an array of IDs, and the
|
||||
// second an array of hashes - each hash corresponding to an ID in the
|
||||
// first array.
|
||||
// User.update([1,2,3], [{name: "foo"}, {name: "bar"}])
|
||||
exists(ExprNodes::ArrayLiteralCfgNode hashesArray |
|
||||
this.getArgument(0).asExpr() instanceof ExprNodes::ArrayLiteralCfgNode and
|
||||
hashesArray = this.getArgument(1).asExpr()
|
||||
@@ -412,6 +421,28 @@ private module Persistence {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `ActiveRecord::Relation#touch_all`, which updates the `updated_at`
|
||||
* attribute on all records in the relation, setting it to the current time or
|
||||
* the time specified. If passed additional attribute names, they will also be
|
||||
* updated with the time.
|
||||
* Examples:
|
||||
* ```rb
|
||||
* Person.all.touch_all
|
||||
* Person.where(name: "David").touch_all
|
||||
* Person.all.touch_all(:created_at)
|
||||
* Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
|
||||
* ```
|
||||
*/
|
||||
private class TouchAllCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
|
||||
TouchAllCall() {
|
||||
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
|
||||
this.getMethodName() = "touch_all"
|
||||
}
|
||||
|
||||
override DataFlow::Node getValue() { result = this.getKeywordArgument("time") }
|
||||
}
|
||||
|
||||
/** A call to e.g. `User.insert_all([{name: "foo"}, {name: "bar"}])` */
|
||||
private class InsertAllLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
|
||||
private ExprNodes::ArrayLiteralCfgNode arr;
|
||||
@@ -444,8 +475,12 @@ private module Persistence {
|
||||
// attrs as hash elements in arg0
|
||||
hashArgumentWithValue(this, 0, result)
|
||||
or
|
||||
// attrs as variable in arg0
|
||||
result = this.getPositionalArgument(0) and
|
||||
not result.asExpr() instanceof ExprNodes::HashLiteralCfgNode
|
||||
or
|
||||
// keyword arg
|
||||
keywordArgumentWithValue(this, result)
|
||||
result = this.getKeywordArgument(_)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger
|
||||
|
||||
|
||||
31
ruby/ql/lib/codeql/ruby/frameworks/Arel.qll
Normal file
31
ruby/ql/lib/codeql/ruby/frameworks/Arel.qll
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Provides modeling for Arel, a low level SQL library that powers ActiveRecord.
|
||||
* Version: 7.0.3
|
||||
* https://api.rubyonrails.org/classes/Arel.html
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
|
||||
/**
|
||||
* Provides modeling for Arel, a low level SQL library that powers ActiveRecord.
|
||||
* Version: 7.0.3
|
||||
* https://api.rubyonrails.org/classes/Arel.html
|
||||
*/
|
||||
module Arel {
|
||||
/**
|
||||
* Flow summary for `Arel.sql`. This method wraps a SQL string, marking it as
|
||||
* safe.
|
||||
*/
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "Arel.sql" }
|
||||
|
||||
override MethodCall getACall() {
|
||||
result = API::getTopLevelMember("Arel").getAMethodCall("sql").asExpr().getExpr()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,12 @@ private API::Node graphQlSchema() { result = API::getTopLevelMember("GraphQL").g
|
||||
private class GraphqlRelayClassicMutationClass extends ClassDeclaration {
|
||||
GraphqlRelayClassicMutationClass() {
|
||||
this.getSuperclassExpr() =
|
||||
graphQlSchema().getMember("RelayClassicMutation").getASubclass*().getAUse().asExpr().getExpr()
|
||||
graphQlSchema()
|
||||
.getMember("RelayClassicMutation")
|
||||
.getASubclass*()
|
||||
.getAValueReachableFromSource()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +76,12 @@ private class GraphqlRelayClassicMutationClass extends ClassDeclaration {
|
||||
private class GraphqlSchemaResolverClass extends ClassDeclaration {
|
||||
GraphqlSchemaResolverClass() {
|
||||
this.getSuperclassExpr() =
|
||||
graphQlSchema().getMember("Resolver").getASubclass().getAUse().asExpr().getExpr()
|
||||
graphQlSchema()
|
||||
.getMember("Resolver")
|
||||
.getASubclass()
|
||||
.getAValueReachableFromSource()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +102,12 @@ private class GraphqlSchemaResolverClass extends ClassDeclaration {
|
||||
class GraphqlSchemaObjectClass extends ClassDeclaration {
|
||||
GraphqlSchemaObjectClass() {
|
||||
this.getSuperclassExpr() =
|
||||
graphQlSchema().getMember("Object").getASubclass().getAUse().asExpr().getExpr()
|
||||
graphQlSchema()
|
||||
.getMember("Object")
|
||||
.getASubclass()
|
||||
.getAValueReachableFromSource()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
}
|
||||
|
||||
/** Gets a `GraphqlFieldDefinitionMethodCall` called in this class. */
|
||||
|
||||
@@ -62,9 +62,9 @@ module PosixSpawn {
|
||||
// is shell interpreted unless there is another argument with a string
|
||||
// constant value.
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
this.argument(arg) and
|
||||
not exists(DataFlow::Node otherArg |
|
||||
otherArg != arg and
|
||||
this.argument(arg) and
|
||||
this.argument(otherArg) and
|
||||
otherArg.asExpr().getConstantValue().isString(_)
|
||||
)
|
||||
|
||||
@@ -63,11 +63,7 @@ private module Config {
|
||||
)
|
||||
or
|
||||
// `Rails.application.config`
|
||||
this =
|
||||
API::getTopLevelMember("Rails")
|
||||
.getReturn("application")
|
||||
.getReturn("config")
|
||||
.getAnImmediateUse()
|
||||
this = API::getTopLevelMember("Rails").getReturn("application").getReturn("config").asSource()
|
||||
or
|
||||
// `Rails.application.configure { ... config ... }`
|
||||
// `Rails::Application.configure { ... config ... }`
|
||||
|
||||
62
ruby/ql/lib/codeql/ruby/frameworks/Railties.qll
Normal file
62
ruby/ql/lib/codeql/ruby/frameworks/Railties.qll
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Modeling for `railties`, which is a gem containing various internals and utilities for the Rails framework.
|
||||
* https://rubygems.org/gems/railties
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ast.internal.Module
|
||||
|
||||
/**
|
||||
* Modeling for `railties`.
|
||||
*/
|
||||
module Railties {
|
||||
/**
|
||||
* A class which `include`s `Rails::Generators::Actions`.
|
||||
*/
|
||||
private class GeneratorsActionsContext extends ClassDeclaration {
|
||||
GeneratorsActionsContext() {
|
||||
exists(IncludeOrPrependCall i |
|
||||
i.getEnclosingModule() = this and
|
||||
i.getArgument(0) =
|
||||
API::getTopLevelMember("Rails")
|
||||
.getMember("Generators")
|
||||
.getMember("Actions")
|
||||
.getAValueReachableFromSource()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Rails::Generators::Actions#execute_command`.
|
||||
* This method concatenates its first and second arguments and executes the result as a shell command.
|
||||
*/
|
||||
private class ExecuteCommandCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
ExecuteCommandCall() {
|
||||
this.asExpr().getExpr().getEnclosingModule() instanceof GeneratorsActionsContext and
|
||||
this.getMethodName() = "execute_command"
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument([0, 1]) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method in `Rails::Generators::Actions` which delegates to `execute_command`.
|
||||
*/
|
||||
private class ExecuteCommandWrapperCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
ExecuteCommandWrapperCall() {
|
||||
this.asExpr().getExpr().getEnclosingModule() instanceof GeneratorsActionsContext and
|
||||
this.getMethodName() = ["rake", "rails_command", "git"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument(0) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,4 @@
|
||||
|
||||
import stdlib.Open3
|
||||
import stdlib.Logger
|
||||
import stdlib.Pathname
|
||||
|
||||
@@ -143,7 +143,7 @@ private DataFlow::LocalSourceNode trackFeature(Feature f, boolean enable, TypeTr
|
||||
or
|
||||
// Use of a constant f
|
||||
enable = true and
|
||||
result = parseOptionsModule().getMember(f.getConstantName()).getAUse()
|
||||
result = parseOptionsModule().getMember(f.getConstantName()).getAValueReachableFromSource()
|
||||
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
|
||||
|
||||
@@ -99,7 +99,8 @@ module Hash {
|
||||
HashNewSummary() { this = "Hash[]" }
|
||||
|
||||
final override ElementReference getACall() {
|
||||
result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and
|
||||
result.getReceiver() =
|
||||
API::getTopLevelMember("Hash").getAValueReachableFromSource().asExpr().getExpr() and
|
||||
result.getNumberOfArguments() = 1
|
||||
}
|
||||
|
||||
@@ -138,7 +139,8 @@ module Hash {
|
||||
}
|
||||
|
||||
final override ElementReference getACall() {
|
||||
result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and
|
||||
result.getReceiver() =
|
||||
API::getTopLevelMember("Hash").getAValueReachableFromSource().asExpr().getExpr() and
|
||||
key = result.getArgument(i - 1).getConstantValue() and
|
||||
exists(result.getArgument(i))
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class IOOrFileWriteMethodCall extends IOOrFileMethodCall {
|
||||
receiverKind = "class" and
|
||||
api = ["IO", "File"] and
|
||||
this = API::getTopLevelMember(api).getAMethodCall(methodName) and
|
||||
methodName = ["binwrite", "write"] and
|
||||
methodName = ["binwrite", "write", "atomic_write"] and
|
||||
dataNode = this.getArgument(1)
|
||||
or
|
||||
// e.g. `{IO,File}.new("foo.txt", "a+).puts("hello")`
|
||||
|
||||
@@ -25,7 +25,7 @@ private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
* A remote flow source originating from a CSV source row.
|
||||
*/
|
||||
private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range {
|
||||
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() }
|
||||
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").asSource() }
|
||||
|
||||
override string getSourceType() { result = "Remote flow (from model)" }
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node connectionUse;
|
||||
|
||||
ExconHttpRequest() {
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
connectionUse = connectionNode.getAnImmediateUse() and
|
||||
requestUse = requestNode.asSource() and
|
||||
connectionUse = connectionNode.asSource() and
|
||||
connectionNode =
|
||||
[
|
||||
// one-off requests
|
||||
@@ -66,7 +66,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
// Check for `ssl_verify_peer: false` in the options hash.
|
||||
exists(DataFlow::Node arg, int i |
|
||||
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
|
||||
i > 0 and
|
||||
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
||||
|
|
||||
argSetsVerifyPeer(arg, false, disablingNode)
|
||||
)
|
||||
@@ -79,7 +80,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
disableCall.asExpr().getASuccessor+() = requestUse.asExpr() and
|
||||
disablingNode = disableCall and
|
||||
not exists(DataFlow::Node arg, int i |
|
||||
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
|
||||
i > 0 and
|
||||
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
||||
|
|
||||
argSetsVerifyPeer(arg, true, _)
|
||||
)
|
||||
|
||||
@@ -38,8 +38,8 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
||||
] and
|
||||
requestNode =
|
||||
connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
connectionUse = connectionNode.getAnImmediateUse() and
|
||||
requestUse = requestNode.asSource() and
|
||||
connectionUse = connectionNode.asSource() and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
||||
// or
|
||||
// `{ ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE } }`
|
||||
exists(DataFlow::Node arg, int i |
|
||||
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
|
||||
i > 0 and
|
||||
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
||||
|
|
||||
// Either passed as an individual key:value argument, e.g.:
|
||||
// Faraday.new(..., ssl: {...})
|
||||
@@ -132,7 +133,11 @@ private predicate isVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSymbolLiteral(key, "verify_mode") and
|
||||
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
|
||||
value =
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class HttpClientRequest extends HTTP::Client::Request::Range {
|
||||
API::getTopLevelMember("HTTPClient").getInstance()
|
||||
] and
|
||||
requestNode = connectionNode.getReturn(method) and
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
requestUse = requestNode.asSource() and
|
||||
method in [
|
||||
"get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
|
||||
] and
|
||||
@@ -52,10 +52,12 @@ class HttpClientRequest extends HTTP::Client::Request::Range {
|
||||
// Look for calls to set
|
||||
// `c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE`
|
||||
// on an HTTPClient connection object `c`.
|
||||
disablingNode =
|
||||
connectionNode.getReturn("ssl_config").getReturn("verify_mode=").getAnImmediateUse() and
|
||||
disablingNode = connectionNode.getReturn("ssl_config").getReturn("verify_mode=").asSource() and
|
||||
disablingNode.(DataFlow::CallNode).getArgument(0) =
|
||||
API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
override string getFramework() { result = "HTTPClient" }
|
||||
|
||||
@@ -28,7 +28,7 @@ class HttpartyRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
HttpartyRequest() {
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
requestUse = requestNode.asSource() and
|
||||
requestNode =
|
||||
API::getTopLevelMember("HTTParty")
|
||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||
|
||||
@@ -25,7 +25,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
|
||||
|
||||
NetHttpRequest() {
|
||||
exists(string method |
|
||||
request = requestNode.getAnImmediateUse() and
|
||||
request = requestNode.asSource() and
|
||||
this = request.asExpr().getExpr()
|
||||
|
|
||||
// Net::HTTP.get(...)
|
||||
@@ -59,7 +59,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
|
||||
new = API::getTopLevelMember("Net").getMember("HTTP").getInstance() and
|
||||
requestNode = new.getReturn(_)
|
||||
|
|
||||
result = new.getAnImmediateUse().(DataFlow::CallNode).getArgument(0)
|
||||
result = new.asSource().(DataFlow::CallNode).getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,7 +73,10 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
|
||||
// foo.request(...)
|
||||
exists(DataFlow::CallNode setter |
|
||||
disablingNode =
|
||||
API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() and
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource() and
|
||||
setter.asExpr().getExpr().(SetterMethodCall).getMethodName() = "verify_mode=" and
|
||||
disablingNode = setter.getArgument(0) and
|
||||
localFlow(setter.getReceiver(), request.getReceiver())
|
||||
|
||||
@@ -28,7 +28,7 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
|
||||
[API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
|
||||
.getReturn("open"), API::getTopLevelMember("OpenURI").getReturn("open_uri")
|
||||
] and
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
requestUse = requestNode.asSource() and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
}
|
||||
|
||||
@@ -110,7 +110,11 @@ private predicate isSslVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSslVerifyModeLiteral(key) and
|
||||
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
|
||||
value =
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range {
|
||||
API::Node connectionNode;
|
||||
|
||||
RestClientHttpRequest() {
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
requestUse = requestNode.asSource() and
|
||||
this = requestUse.asExpr().getExpr() and
|
||||
(
|
||||
connectionNode =
|
||||
@@ -52,7 +52,8 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range {
|
||||
// `RestClient::Resource::new` takes an options hash argument, and we're
|
||||
// looking for `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }`.
|
||||
exists(DataFlow::Node arg, int i |
|
||||
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
|
||||
i > 0 and
|
||||
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
||||
|
|
||||
// Either passed as an individual key:value argument, e.g.:
|
||||
// RestClient::Resource.new(..., verify_ssl: OpenSSL::SSL::VERIFY_NONE)
|
||||
@@ -79,7 +80,11 @@ private predicate isVerifySslNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
|
||||
key.asExpr() = p.getKey() and
|
||||
value.asExpr() = p.getValue() and
|
||||
isSslVerifyModeLiteral(key) and
|
||||
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
|
||||
value =
|
||||
API::getTopLevelMember("OpenSSL")
|
||||
.getMember("SSL")
|
||||
.getMember("VERIFY_NONE")
|
||||
.getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
|
||||
API::Node requestNode;
|
||||
|
||||
TyphoeusHttpRequest() {
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
requestUse = requestNode.asSource() and
|
||||
requestNode =
|
||||
API::getTopLevelMember("Typhoeus")
|
||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||
|
||||
188
ruby/ql/lib/codeql/ruby/frameworks/stdlib/Pathname.qll
Normal file
188
ruby/ql/lib/codeql/ruby/frameworks/stdlib/Pathname.qll
Normal file
@@ -0,0 +1,188 @@
|
||||
/** Modeling of the `Pathname` class from the Ruby standard library. */
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Modeling of the `Pathname` class from the Ruby standard library.
|
||||
*
|
||||
* https://docs.ruby-lang.org/en/3.1/Pathname.html
|
||||
*/
|
||||
module Pathname {
|
||||
/**
|
||||
* An instance of the `Pathname` class. For example, in
|
||||
*
|
||||
* ```rb
|
||||
* pn = Pathname.new "foo.txt'"
|
||||
* puts pn.read
|
||||
* ```
|
||||
*
|
||||
* there are three `PathnameInstance`s - the call to `Pathname.new`, the
|
||||
* assignment `pn = ...`, and the read access to `pn` on the second line.
|
||||
*
|
||||
* Every `PathnameInstance` is considered to be a `FileNameSource`.
|
||||
*/
|
||||
class PathnameInstance extends FileNameSource, DataFlow::Node {
|
||||
PathnameInstance() { this = pathnameInstance() }
|
||||
}
|
||||
|
||||
private DataFlow::Node pathnameInstance() {
|
||||
// A call to `Pathname.new`.
|
||||
result = API::getTopLevelMember("Pathname").getAnInstantiation()
|
||||
or
|
||||
// Class methods on `Pathname` that return a new `Pathname`.
|
||||
result = API::getTopLevelMember("Pathname").getAMethodCall(["getwd", "pwd",])
|
||||
or
|
||||
// Instance methods on `Pathname` that return a new `Pathname`.
|
||||
exists(DataFlow::CallNode c | result = c |
|
||||
c.getReceiver() = pathnameInstance() and
|
||||
c.getMethodName() =
|
||||
[
|
||||
"+", "/", "basename", "cleanpath", "expand_path", "join", "realpath",
|
||||
"relative_path_from", "sub", "sub_ext", "to_path"
|
||||
]
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node inst |
|
||||
inst = pathnameInstance() and
|
||||
inst.(DataFlow::LocalSourceNode).flowsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
/** A call where the receiver is a `Pathname`. */
|
||||
class PathnameCall extends DataFlow::CallNode {
|
||||
PathnameCall() { this.getReceiver() instanceof PathnameInstance }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Pathname#open` or `Pathname#opendir`, considered as a
|
||||
* `FileSystemAccess`.
|
||||
*/
|
||||
class PathnameOpen extends FileSystemAccess::Range, PathnameCall {
|
||||
PathnameOpen() { this.getMethodName() = ["open", "opendir"] }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
|
||||
}
|
||||
|
||||
/** A call to `Pathname#read`, considered as a `FileSystemReadAccess`. */
|
||||
class PathnameRead extends FileSystemReadAccess::Range, PathnameCall {
|
||||
PathnameRead() { this.getMethodName() = "read" }
|
||||
|
||||
// The path is the receiver (the `Pathname` object).
|
||||
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
|
||||
|
||||
// The read data is the return value of the call.
|
||||
override DataFlow::Node getADataNode() { result = this }
|
||||
}
|
||||
|
||||
/** A call to `Pathname#write`, considered as a `FileSystemWriteAccess`. */
|
||||
class PathnameWrite extends FileSystemWriteAccess::Range, PathnameCall {
|
||||
PathnameWrite() { this.getMethodName() = "write" }
|
||||
|
||||
// The path is the receiver (the `Pathname` object).
|
||||
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
|
||||
|
||||
// The data to write is the 0th argument.
|
||||
override DataFlow::Node getADataNode() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** A call to `Pathname#to_s`, considered as a `FileNameSource`. */
|
||||
class PathnameToSFilenameSource extends FileNameSource, PathnameCall {
|
||||
PathnameToSFilenameSource() { this.getMethodName() = "to_s" }
|
||||
}
|
||||
|
||||
private class PathnamePermissionModification extends FileSystemPermissionModification::Range,
|
||||
PathnameCall {
|
||||
private DataFlow::Node permissionArg;
|
||||
|
||||
PathnamePermissionModification() {
|
||||
exists(string methodName | this.getMethodName() = methodName |
|
||||
methodName = ["chmod", "mkdir"] and permissionArg = this.getArgument(0)
|
||||
or
|
||||
methodName = "mkpath" and permissionArg = this.getKeywordArgument("mode")
|
||||
or
|
||||
methodName = "open" and permissionArg = this.getArgument(1)
|
||||
// TODO: defaults for optional args? This may depend on the umask
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPermissionNode() { result = permissionArg }
|
||||
}
|
||||
|
||||
/**
|
||||
* Type summaries for the `Pathname` class, i.e. method calls that produce new
|
||||
* `Pathname` instances.
|
||||
*/
|
||||
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package1;type1;package2;type2;path
|
||||
row =
|
||||
[
|
||||
// Pathname.new : Pathname
|
||||
";Pathname;;;Member[Pathname].Instance",
|
||||
// Pathname#+(path) : Pathname
|
||||
";Pathname;;Pathname;Method[+].ReturnValue",
|
||||
// Pathname#/(path) : Pathname
|
||||
";Pathname;;Pathname;Method[/].ReturnValue",
|
||||
// Pathname#basename(path) : Pathname
|
||||
";Pathname;;Pathname;Method[basename].ReturnValue",
|
||||
// Pathname#cleanpath(path) : Pathname
|
||||
";Pathname;;Pathname;Method[cleanpath].ReturnValue",
|
||||
// Pathname#expand_path(path) : Pathname
|
||||
";Pathname;;Pathname;Method[expand_path].ReturnValue",
|
||||
// Pathname#join(path) : Pathname
|
||||
";Pathname;;Pathname;Method[join].ReturnValue",
|
||||
// Pathname#realpath(path) : Pathname
|
||||
";Pathname;;Pathname;Method[realpath].ReturnValue",
|
||||
// Pathname#relative_path_from(path) : Pathname
|
||||
";Pathname;;Pathname;Method[relative_path_from].ReturnValue",
|
||||
// Pathname#sub(path) : Pathname
|
||||
";Pathname;;Pathname;Method[sub].ReturnValue",
|
||||
// Pathname#sub_ext(path) : Pathname
|
||||
";Pathname;;Pathname;Method[sub_ext].ReturnValue",
|
||||
// Pathname#to_path(path) : Pathname
|
||||
";Pathname;;Pathname;Method[to_path].ReturnValue",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** Taint flow summaries for the `Pathname` class. */
|
||||
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
// Pathname.new(path)
|
||||
";;Member[Pathname].Method[new];Argument[0];ReturnValue;taint",
|
||||
// Pathname#dirname
|
||||
";Pathname;Method[dirname];Argument[self];ReturnValue;taint",
|
||||
// Pathname#each_filename
|
||||
";Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
|
||||
// Pathname#expand_path
|
||||
";Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
|
||||
// Pathname#join
|
||||
";Pathname;Method[join];Argument[self,any];ReturnValue;taint",
|
||||
// Pathname#parent
|
||||
";Pathname;Method[parent];Argument[self];ReturnValue;taint",
|
||||
// Pathname#realpath
|
||||
";Pathname;Method[realpath];Argument[self];ReturnValue;taint",
|
||||
// Pathname#relative_path_from
|
||||
";Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
|
||||
// Pathname#to_path
|
||||
";Pathname;Method[to_path];Argument[self];ReturnValue;taint",
|
||||
// Pathname#basename
|
||||
";Pathname;Method[basename];Argument[self];ReturnValue;taint",
|
||||
// Pathname#cleanpath
|
||||
";Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
|
||||
// Pathname#sub
|
||||
";Pathname;Method[sub];Argument[self];ReturnValue;taint",
|
||||
// Pathname#sub_ext
|
||||
";Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
private import AST
|
||||
private import codeql.ruby.Regexp as RE
|
||||
private import codeql.ruby.ast.internal.Synthesis
|
||||
private import ast.internal.AST
|
||||
|
||||
/**
|
||||
* The query can extend this class to control which nodes are printed.
|
||||
@@ -35,6 +36,8 @@ private predicate shouldPrintAstEdge(AstNode parent, string edgeName, AstNode ch
|
||||
any(PrintAstConfiguration config).shouldPrintAstEdge(parent, edgeName, child)
|
||||
}
|
||||
|
||||
private int nonSynthIndex() { result = min([-1, any(int i | exists(getSynthChild(_, i)))]) - 1 }
|
||||
|
||||
newtype TPrintNode =
|
||||
TPrintRegularAstNode(AstNode n) { shouldPrintNode(n) } or
|
||||
TPrintRegExpNode(RE::RegExpTerm term) {
|
||||
@@ -112,13 +115,22 @@ class PrintRegularAstNode extends PrintAstNode, TPrintRegularAstNode {
|
||||
)
|
||||
}
|
||||
|
||||
private int getSynthAstNodeIndex() {
|
||||
not astNode.isSynthesized() and result = nonSynthIndex()
|
||||
or
|
||||
astNode = getSynthChild(astNode.getParent(), result)
|
||||
}
|
||||
|
||||
override int getOrder() {
|
||||
this =
|
||||
rank[result](PrintRegularAstNode p, Location l, File f |
|
||||
l = p.getLocation() and
|
||||
f = l.getFile()
|
||||
|
|
||||
p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn()
|
||||
p
|
||||
order by
|
||||
f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn(),
|
||||
l.getEndLine(), l.getEndColumn(), p.getSynthAstNodeIndex()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,9 @@ module UrlRedirect {
|
||||
// We exclude any handlers with names containing create/update/destroy, as these are not likely to handle GET requests.
|
||||
not exists(method.(ActionControllerActionMethod).getARoute()) and
|
||||
not method.getName().regexpMatch(".*(create|update|destroy).*")
|
||||
)
|
||||
) and
|
||||
// If this redirect is an ActionController method call, it is only vulnerable if it allows external redirects.
|
||||
forall(RedirectToCall c | c = e.asExpr().getExpr() | c.allowsExternalRedirect())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-all
|
||||
version: 0.3.0-dev
|
||||
version: 0.3.3-dev
|
||||
groups: ruby
|
||||
extractor: ruby
|
||||
dbscheme: ruby.dbscheme
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
## 0.3.1
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new experimental query, `rb/manually-checking-http-verb`, to detect cases when the HTTP verb for an incoming request is checked and then used as part of control flow.
|
||||
* Added a new experimental query, `rb/weak-params`, to detect cases when the rails strong parameters pattern isn't followed and values flow into persistent store writes.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Contextual queries and the query libraries they depend on have been moved to the `codeql/ruby-all` package.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `rb/improper-memoization`. The query finds cases where the parameter of a memoization method is not used in the memoization key.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The query "Use of a broken or weak cryptographic algorithm" (`rb/weak-cryptographic-algorithm`) now reports if a cryptographic operation is potentially insecure due to use of a weak block mode.
|
||||
|
||||
## 0.1.4
|
||||
|
||||
## 0.1.3
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The query "Use of a broken or weak cryptographic algorithm" (`rb/weak-cryptographic-algorithm`) now report if a cryptographic operation is potentially insecure due to use of a weak block mode.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `rb/improper-memoization`. The query finds cases where the parameter of a memoization method is not used in the memoization key.
|
||||
9
ruby/ql/src/change-notes/released/0.2.0.md
Normal file
9
ruby/ql/src/change-notes/released/0.2.0.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 0.2.0
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `rb/improper-memoization`. The query finds cases where the parameter of a memoization method is not used in the memoization key.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The query "Use of a broken or weak cryptographic algorithm" (`rb/weak-cryptographic-algorithm`) now reports if a cryptographic operation is potentially insecure due to use of a weak block mode.
|
||||
5
ruby/ql/src/change-notes/released/0.3.0.md
Normal file
5
ruby/ql/src/change-notes/released/0.3.0.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.3.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Contextual queries and the query libraries they depend on have been moved to the `codeql/ruby-all` package.
|
||||
6
ruby/ql/src/change-notes/released/0.3.1.md
Normal file
6
ruby/ql/src/change-notes/released/0.3.1.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 0.3.1
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new experimental query, `rb/manually-checking-http-verb`, to detect cases when the HTTP verb for an incoming request is checked and then used as part of control flow.
|
||||
* Added a new experimental query, `rb/weak-params`, to detect cases when the rails strong parameters pattern isn't followed and values flow into persistent store writes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.1.4
|
||||
lastReleaseVersion: 0.3.1
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Manually checking the HTTP request verb inside of a controller method can lead to
|
||||
CSRF bypass if GET or HEAD requests are handled improperly.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
It is better to use different controller methods for each resource/http verb combination
|
||||
and configure the Rails routes in your application to call them accordingly.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
See https://guides.rubyonrails.org/routing.html for more information.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @name Manually checking http verb instead of using built in rails routes and protections
|
||||
* @description Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.0
|
||||
* @precision low
|
||||
* @id rb/manually-checking-http-verb
|
||||
* @tags security
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.controlflow.CfgNodes
|
||||
import codeql.ruby.frameworks.ActionController
|
||||
import codeql.ruby.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
// any `request` calls in an action method
|
||||
class Request extends DataFlow::CallNode {
|
||||
Request() {
|
||||
this.getMethodName() = "request" and
|
||||
this.asExpr().getExpr().getEnclosingMethod() instanceof ActionControllerActionMethod
|
||||
}
|
||||
}
|
||||
|
||||
// `request.env`
|
||||
class RequestEnvMethod extends DataFlow::CallNode {
|
||||
RequestEnvMethod() {
|
||||
this.getMethodName() = "env" and
|
||||
any(Request r).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
// `request.request_method`
|
||||
class RequestRequestMethod extends DataFlow::CallNode {
|
||||
RequestRequestMethod() {
|
||||
this.getMethodName() = "request_method" and
|
||||
any(Request r).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
// `request.method`
|
||||
class RequestMethod extends DataFlow::CallNode {
|
||||
RequestMethod() {
|
||||
this.getMethodName() = "method" and
|
||||
any(Request r).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
// `request.raw_request_method`
|
||||
class RequestRawRequestMethod extends DataFlow::CallNode {
|
||||
RequestRawRequestMethod() {
|
||||
this.getMethodName() = "raw_request_method" and
|
||||
any(Request r).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
// `request.request_method_symbol`
|
||||
class RequestRequestMethodSymbol extends DataFlow::CallNode {
|
||||
RequestRequestMethodSymbol() {
|
||||
this.getMethodName() = "request_method_symbol" and
|
||||
any(Request r).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
// `request.get?`
|
||||
class RequestGet extends DataFlow::CallNode {
|
||||
RequestGet() {
|
||||
this.getMethodName() = "get?" and
|
||||
any(Request r).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
class HttpVerbConfig extends TaintTracking::Configuration {
|
||||
HttpVerbConfig() { this = "HttpVerbConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof RequestMethod or
|
||||
source instanceof RequestRequestMethod or
|
||||
source instanceof RequestEnvMethod or
|
||||
source instanceof RequestRawRequestMethod or
|
||||
source instanceof RequestRequestMethodSymbol or
|
||||
source instanceof RequestGet
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(ExprNodes::ConditionalExprCfgNode c | c.getCondition() = sink.asExpr()) or
|
||||
exists(ExprNodes::CaseExprCfgNode c | c.getValue() = sink.asExpr())
|
||||
}
|
||||
}
|
||||
|
||||
from HttpVerbConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods."
|
||||
28
ruby/ql/src/experimental/weak-params/WeakParams.qhelp
Normal file
28
ruby/ql/src/experimental/weak-params/WeakParams.qhelp
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Directly checking request parameters without following a strong params
|
||||
pattern can lead to unintentional avenues for injection attacks.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Instead of manually checking parameters from the `param` object, it is
|
||||
recommended that you follow the strong parameters pattern established in
|
||||
Rails: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html
|
||||
</p>
|
||||
<p>
|
||||
In the strong parameters pattern, you are able to specify required and allowed
|
||||
parameters for each action called by your controller methods. This acts as an
|
||||
additional layer of data validation before being passed along to other areas
|
||||
of your application, such as the model.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<references>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
61
ruby/ql/src/experimental/weak-params/WeakParams.ql
Normal file
61
ruby/ql/src/experimental/weak-params/WeakParams.ql
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @name Weak or direct parameter references are used
|
||||
* @description Directly checking request parameters without following a strong params pattern can lead to unintentional avenues for injection attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.0
|
||||
* @precision medium
|
||||
* @id rb/weak-params
|
||||
* @tags security
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.Concepts
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.frameworks.ActionController
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A call to `request` in an ActionController controller class.
|
||||
* This probably refers to the incoming HTTP request object.
|
||||
*/
|
||||
class ActionControllerRequest extends DataFlow::Node {
|
||||
ActionControllerRequest() {
|
||||
exists(DataFlow::CallNode c |
|
||||
c.asExpr().getExpr().getEnclosingModule() instanceof ActionControllerControllerClass and
|
||||
c.getMethodName() = "request"
|
||||
|
|
||||
c.flowsTo(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A direct parameters reference that happens inside a controller class.
|
||||
*/
|
||||
class WeakParams extends DataFlow::CallNode {
|
||||
WeakParams() {
|
||||
this.getReceiver() instanceof ActionControllerRequest and
|
||||
this.getMethodName() =
|
||||
["path_parameters", "query_parameters", "request_parameters", "GET", "POST"]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Taint tracking config where the source is a weak params access in a controller and the sink
|
||||
* is a method call of a model class
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "WeakParamsConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof WeakParams }
|
||||
|
||||
// the sink is an instance of a Model class that receives a method call
|
||||
override predicate isSink(DataFlow::Node node) { node = any(PersistentWriteAccess a).getValue() }
|
||||
}
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"By exposing all keys in request parameters or by blindy accessing them, unintended parameters could be used and lead to mass-assignment or have other unexpected side-effects. It is safer to follow the 'strong parameters' pattern in Rails, which is outlined here: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html"
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-queries
|
||||
version: 0.2.0-dev
|
||||
version: 0.3.2-dev
|
||||
groups:
|
||||
- ruby
|
||||
- queries
|
||||
|
||||
@@ -53,7 +53,7 @@ predicate matchesBeginningOfString(RegExpTerm term) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given sequence contains top-level domain preceded by a dot, such as `.com`,
|
||||
* Holds if the given sequence `seq` contains top-level domain preceded by a dot, such as `.com`,
|
||||
* excluding cases where this is at the very beginning of the regexp.
|
||||
*
|
||||
* `i` is bound to the index of the last child in the top-level domain part.
|
||||
|
||||
@@ -1556,6 +1556,35 @@ constants/constants.rb:
|
||||
# 73| getAnOperand/getLeftOperand: [ClassVariableAccess] @@fourty_six
|
||||
# 73| getAnOperand/getRightOperand: [ConstantReadAccess] FOURTY_SIX
|
||||
# 73| getScopeExpr: [ConstantReadAccess] Mod3
|
||||
# 78| getStmt: [AssignExpr] ... = ...
|
||||
# 78| getAnOperand/getLeftOperand: [LocalVariableAccess] a
|
||||
# 78| getAnOperand/getRightOperand: [ArrayLiteral] [...]
|
||||
# 78| getElement: [IntegerLiteral] 1
|
||||
# 78| getElement: [IntegerLiteral] 2
|
||||
# 78| getElement: [IntegerLiteral] 3
|
||||
# 79| getStmt: [AssignExpr] ... = ...
|
||||
# 79| getAnOperand/getLeftOperand: [ConstantAssignment] A
|
||||
# 79| getAnOperand/getRightOperand: [ArrayLiteral] [...]
|
||||
# 79| getElement: [IntegerLiteral] 1
|
||||
# 79| getElement: [IntegerLiteral] 2
|
||||
# 79| getElement: [IntegerLiteral] 3
|
||||
# 80| getStmt: [AssignExpr] ... = ...
|
||||
# 80| getAnOperand/getLeftOperand: [ConstantAssignment] B
|
||||
# 80| getAnOperand/getRightOperand: [LocalVariableAccess] a
|
||||
# 81| getStmt: [AssignExpr] ... = ...
|
||||
# 81| getAnOperand/getLeftOperand: [ConstantAssignment] C
|
||||
# 81| getAnOperand/getRightOperand: [ConstantReadAccess] A
|
||||
# 82| getStmt: [AssignExpr] ... = ...
|
||||
# 82| getAnOperand/getLeftOperand: [LocalVariableAccess] b
|
||||
# 82| getAnOperand/getRightOperand: [ConstantReadAccess] B
|
||||
# 84| getStmt: [IfExpr] if ...
|
||||
# 84| getCondition: [MethodCall] call to condition
|
||||
# 84| getReceiver: [SelfVariableAccess] self
|
||||
# 84| getBranch/getThen: [StmtSequence] then ...
|
||||
# 85| getStmt: [AssignExpr] ... = ...
|
||||
# 85| getAnOperand/getLeftOperand: [LocalVariableAccess] c
|
||||
# 85| getAnOperand/getRightOperand: [LocalVariableAccess] b
|
||||
# 87| getStmt: [LocalVariableAccess] c
|
||||
escape_sequences/escapes.rb:
|
||||
# 1| [Toplevel] escapes.rb
|
||||
# 6| getStmt: [StringLiteral] "\'"
|
||||
@@ -1733,6 +1762,12 @@ escape_sequences/escapes.rb:
|
||||
# 93| getStmt: [SymbolLiteral] :"\C-?"
|
||||
# 93| getComponent: [StringEscapeSequenceComponent] \C
|
||||
# 93| getComponent: [StringTextComponent] -?
|
||||
misc/iso-8859-15.rb:
|
||||
# 1| [Toplevel] iso-8859-15.rb
|
||||
# 4| getStmt: [MethodCall] call to print
|
||||
# 4| getReceiver: [SelfVariableAccess] self
|
||||
# 4| getArgument: [StringLiteral] "EUR = €"
|
||||
# 4| getComponent: [StringTextComponent] EUR = €
|
||||
literals/literals.rb:
|
||||
# 1| [Toplevel] literals.rb
|
||||
# 2| getStmt: [NilLiteral] nil
|
||||
|
||||
@@ -86,10 +86,10 @@ calls/calls.rb:
|
||||
# 316| getStmt: [SetterMethodCall] call to foo=
|
||||
# 316| getReceiver: [SelfVariableAccess] self
|
||||
# 316| getArgument: [AssignExpr] ... = ...
|
||||
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 316| getArgument: [IntegerLiteral] 0
|
||||
# 316| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 316| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 316| getArgument: [IntegerLiteral] 0
|
||||
# 316| getStmt: [LocalVariableAccess] __synth__0__1
|
||||
# 316| getStmt: [AssignExpr] ... = ...
|
||||
# 316| getAnOperand/getLeftOperand: [MethodCall] call to bar
|
||||
@@ -97,12 +97,12 @@ calls/calls.rb:
|
||||
# 316| getStmt: [SetterMethodCall] call to bar=
|
||||
# 316| getReceiver: [SelfVariableAccess] self
|
||||
# 316| getArgument: [AssignExpr] ... = ...
|
||||
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 316| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 316| getArgument: [RangeLiteral] _ .. _
|
||||
# 316| getBegin: [IntegerLiteral] 1
|
||||
# 316| getEnd: [IntegerLiteral] -2
|
||||
# 316| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 316| getStmt: [LocalVariableAccess] __synth__0__1
|
||||
# 316| getStmt: [AssignExpr] ... = ...
|
||||
# 316| getAnOperand/getLeftOperand: [ElementReference] ...[...]
|
||||
@@ -111,13 +111,14 @@ calls/calls.rb:
|
||||
# 316| getReceiver: [MethodCall] call to foo
|
||||
# 316| getReceiver: [SelfVariableAccess] self
|
||||
# 316| getArgument: [AssignExpr] ... = ...
|
||||
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 316| getArgument: [IntegerLiteral] -1
|
||||
# 316| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 316| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 316| getArgument: [IntegerLiteral] -1
|
||||
# 316| getArgument: [IntegerLiteral] 4
|
||||
# 316| getStmt: [LocalVariableAccess] __synth__0__1
|
||||
# 316| getStmt: [AssignExpr] ... = ...
|
||||
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0
|
||||
# 316| getAnOperand/getRightOperand: [SplatExpr] * ...
|
||||
# 316| getAnOperand/getOperand/getReceiver: [ArrayLiteral] [...]
|
||||
# 316| getDesugared: [MethodCall] call to []
|
||||
@@ -126,14 +127,13 @@ calls/calls.rb:
|
||||
# 316| getArgument: [IntegerLiteral] 2
|
||||
# 316| getArgument: [IntegerLiteral] 3
|
||||
# 316| getArgument: [IntegerLiteral] 4
|
||||
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0
|
||||
# 317| [AssignExpr] ... = ...
|
||||
# 317| getDesugared: [StmtSequence] ...
|
||||
# 317| getStmt: [AssignExpr] ... = ...
|
||||
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] a
|
||||
# 317| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 317| getArgument: [IntegerLiteral] 0
|
||||
# 317| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 317| getArgument: [IntegerLiteral] 0
|
||||
# 317| getStmt: [AssignExpr] ... = ...
|
||||
# 317| getAnOperand/getLeftOperand: [ElementReference] ...[...]
|
||||
# 317| getDesugared: [StmtSequence] ...
|
||||
@@ -141,15 +141,16 @@ calls/calls.rb:
|
||||
# 317| getReceiver: [MethodCall] call to foo
|
||||
# 317| getReceiver: [SelfVariableAccess] self
|
||||
# 317| getArgument: [AssignExpr] ... = ...
|
||||
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 317| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 317| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 317| getArgument: [RangeLiteral] _ .. _
|
||||
# 317| getBegin: [IntegerLiteral] 1
|
||||
# 317| getEnd: [IntegerLiteral] -1
|
||||
# 317| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 317| getArgument: [IntegerLiteral] 5
|
||||
# 317| getStmt: [LocalVariableAccess] __synth__0__1
|
||||
# 317| getStmt: [AssignExpr] ... = ...
|
||||
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0
|
||||
# 317| getAnOperand/getRightOperand: [SplatExpr] * ...
|
||||
# 317| getAnOperand/getOperand/getReceiver: [ArrayLiteral] [...]
|
||||
# 317| getDesugared: [MethodCall] call to []
|
||||
@@ -157,7 +158,6 @@ calls/calls.rb:
|
||||
# 317| getArgument: [IntegerLiteral] 1
|
||||
# 317| getArgument: [IntegerLiteral] 2
|
||||
# 317| getArgument: [IntegerLiteral] 3
|
||||
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0
|
||||
# 318| [AssignAddExpr] ... += ...
|
||||
# 318| getDesugared: [StmtSequence] ...
|
||||
# 318| getStmt: [AssignExpr] ... = ...
|
||||
@@ -167,11 +167,11 @@ calls/calls.rb:
|
||||
# 318| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 318| getArgument: [LocalVariableAccess] __synth__1
|
||||
# 318| getStmt: [AssignExpr] ... = ...
|
||||
# 318| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1
|
||||
# 318| getAnOperand/getRightOperand: [AddExpr] ... + ...
|
||||
# 318| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to count
|
||||
# 318| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 318| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
|
||||
# 318| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1
|
||||
# 318| getStmt: [LocalVariableAccess] __synth__1
|
||||
# 319| [AssignAddExpr] ... += ...
|
||||
# 319| getDesugared: [StmtSequence] ...
|
||||
@@ -187,12 +187,12 @@ calls/calls.rb:
|
||||
# 319| getAnOperand/getRightOperand: [IntegerLiteral] 0
|
||||
# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1
|
||||
# 319| getStmt: [AssignExpr] ... = ...
|
||||
# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2
|
||||
# 319| getAnOperand/getRightOperand: [AddExpr] ... + ...
|
||||
# 319| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to []
|
||||
# 319| getReceiver: [LocalVariableAccess] __synth__0
|
||||
# 319| getArgument: [LocalVariableAccess] __synth__1
|
||||
# 319| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
|
||||
# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2
|
||||
# 319| getStmt: [LocalVariableAccess] __synth__2
|
||||
# 320| [AssignMulExpr] ... *= ...
|
||||
# 320| getDesugared: [StmtSequence] ...
|
||||
@@ -223,6 +223,7 @@ calls/calls.rb:
|
||||
# 320| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
|
||||
# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__3
|
||||
# 320| getStmt: [AssignExpr] ... = ...
|
||||
# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__4
|
||||
# 320| getAnOperand/getRightOperand: [MulExpr] ... * ...
|
||||
# 320| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to []
|
||||
# 320| getReceiver: [LocalVariableAccess] __synth__0
|
||||
@@ -230,7 +231,6 @@ calls/calls.rb:
|
||||
# 320| getArgument: [LocalVariableAccess] __synth__2
|
||||
# 320| getArgument: [LocalVariableAccess] __synth__3
|
||||
# 320| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2
|
||||
# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__4
|
||||
# 320| getStmt: [LocalVariableAccess] __synth__4
|
||||
# 340| [ForExpr] for ... in ...
|
||||
# 340| getDesugared: [MethodCall] call to each
|
||||
@@ -240,24 +240,24 @@ calls/calls.rb:
|
||||
# 340| getStmt: [AssignExpr] ... = ...
|
||||
# 340| getDesugared: [StmtSequence] ...
|
||||
# 340| getStmt: [AssignExpr] ... = ...
|
||||
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 340| getAnOperand/getRightOperand: [SplatExpr] * ...
|
||||
# 340| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 340| getStmt: [AssignExpr] ... = ...
|
||||
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] x
|
||||
# 340| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 340| getArgument: [IntegerLiteral] 0
|
||||
# 340| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 340| getArgument: [IntegerLiteral] 0
|
||||
# 340| getStmt: [AssignExpr] ... = ...
|
||||
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] y
|
||||
# 340| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 340| getArgument: [IntegerLiteral] 1
|
||||
# 340| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 340| getArgument: [IntegerLiteral] 1
|
||||
# 340| getStmt: [AssignExpr] ... = ...
|
||||
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] z
|
||||
# 340| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 340| getArgument: [IntegerLiteral] 2
|
||||
# 340| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 340| getArgument: [IntegerLiteral] 2
|
||||
# 340| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...)
|
||||
# 341| getStmt: [MethodCall] call to foo
|
||||
# 341| getReceiver: [SelfVariableAccess] self
|
||||
@@ -286,9 +286,9 @@ calls/calls.rb:
|
||||
# 362| getReceiver: [SelfVariableAccess] self
|
||||
# 362| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 362| getStmt: [IfExpr] if ...
|
||||
# 362| getBranch/getThen: [NilLiteral] nil
|
||||
# 362| getBranch/getElse: [MethodCall] call to empty?
|
||||
# 362| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 362| getBranch/getThen: [NilLiteral] nil
|
||||
# 362| getCondition: [MethodCall] call to ==
|
||||
# 362| getArgument: [LocalVariableAccess] __synth__0__1
|
||||
# 362| getReceiver: [NilLiteral] nil
|
||||
@@ -299,6 +299,7 @@ calls/calls.rb:
|
||||
# 364| getReceiver: [SelfVariableAccess] self
|
||||
# 364| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 364| getStmt: [IfExpr] if ...
|
||||
# 364| getBranch/getThen: [NilLiteral] nil
|
||||
# 364| getBranch/getElse: [MethodCall] call to bar
|
||||
# 364| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 364| getArgument: [IntegerLiteral] 1
|
||||
@@ -307,7 +308,6 @@ calls/calls.rb:
|
||||
# 364| getParameter: [SimpleParameter] x
|
||||
# 364| getDefiningAccess: [LocalVariableAccess] x
|
||||
# 364| getStmt: [LocalVariableAccess] x
|
||||
# 364| getBranch/getThen: [NilLiteral] nil
|
||||
# 364| getCondition: [MethodCall] call to ==
|
||||
# 364| getArgument: [LocalVariableAccess] __synth__0__1
|
||||
# 364| getReceiver: [NilLiteral] nil
|
||||
@@ -336,6 +336,18 @@ constants/constants.rb:
|
||||
# 20| getComponent: [StringTextComponent] Chuck
|
||||
# 20| getArgument: [StringLiteral] "Dave"
|
||||
# 20| getComponent: [StringTextComponent] Dave
|
||||
# 78| [ArrayLiteral] [...]
|
||||
# 78| getDesugared: [MethodCall] call to []
|
||||
# 78| getReceiver: [ConstantReadAccess] Array
|
||||
# 78| getArgument: [IntegerLiteral] 1
|
||||
# 78| getArgument: [IntegerLiteral] 2
|
||||
# 78| getArgument: [IntegerLiteral] 3
|
||||
# 79| [ArrayLiteral] [...]
|
||||
# 79| getDesugared: [MethodCall] call to []
|
||||
# 79| getReceiver: [ConstantReadAccess] Array
|
||||
# 79| getArgument: [IntegerLiteral] 1
|
||||
# 79| getArgument: [IntegerLiteral] 2
|
||||
# 79| getArgument: [IntegerLiteral] 3
|
||||
escape_sequences/escapes.rb:
|
||||
# 58| [ArrayLiteral] %w(...)
|
||||
# 58| getDesugared: [MethodCall] call to []
|
||||
@@ -596,19 +608,19 @@ control/loops.rb:
|
||||
# 22| getStmt: [AssignExpr] ... = ...
|
||||
# 22| getDesugared: [StmtSequence] ...
|
||||
# 22| getStmt: [AssignExpr] ... = ...
|
||||
# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 22| getAnOperand/getRightOperand: [SplatExpr] * ...
|
||||
# 22| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 22| getStmt: [AssignExpr] ... = ...
|
||||
# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] key
|
||||
# 22| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 22| getArgument: [IntegerLiteral] 0
|
||||
# 22| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 22| getArgument: [IntegerLiteral] 0
|
||||
# 22| getStmt: [AssignExpr] ... = ...
|
||||
# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] value
|
||||
# 22| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 22| getArgument: [IntegerLiteral] 1
|
||||
# 22| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 22| getArgument: [IntegerLiteral] 1
|
||||
# 22| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...)
|
||||
# 23| getStmt: [AssignAddExpr] ... += ...
|
||||
# 23| getDesugared: [AssignExpr] ... = ...
|
||||
@@ -641,19 +653,19 @@ control/loops.rb:
|
||||
# 28| getStmt: [AssignExpr] ... = ...
|
||||
# 28| getDesugared: [StmtSequence] ...
|
||||
# 28| getStmt: [AssignExpr] ... = ...
|
||||
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 28| getAnOperand/getRightOperand: [SplatExpr] * ...
|
||||
# 28| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
|
||||
# 28| getStmt: [AssignExpr] ... = ...
|
||||
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] key
|
||||
# 28| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 28| getArgument: [IntegerLiteral] 0
|
||||
# 28| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 28| getArgument: [IntegerLiteral] 0
|
||||
# 28| getStmt: [AssignExpr] ... = ...
|
||||
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] value
|
||||
# 28| getAnOperand/getRightOperand: [MethodCall] call to []
|
||||
# 28| getArgument: [IntegerLiteral] 1
|
||||
# 28| getReceiver: [LocalVariableAccess] __synth__0__1
|
||||
# 28| getArgument: [IntegerLiteral] 1
|
||||
# 28| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...)
|
||||
# 29| getStmt: [AssignAddExpr] ... += ...
|
||||
# 29| getDesugared: [AssignExpr] ... = ...
|
||||
|
||||
@@ -1656,10 +1656,56 @@ constants/constants.rb:
|
||||
# 73| 1: [ReservedWord] ::
|
||||
# 73| 2: [Constant] FOURTY_SIX
|
||||
# 74| 5: [ReservedWord] end
|
||||
# 78| 13: [Assignment] Assignment
|
||||
# 78| 0: [Identifier] a
|
||||
# 78| 1: [ReservedWord] =
|
||||
# 78| 2: [Array] Array
|
||||
# 78| 0: [ReservedWord] [
|
||||
# 78| 1: [Integer] 1
|
||||
# 78| 2: [ReservedWord] ,
|
||||
# 78| 3: [Integer] 2
|
||||
# 78| 4: [ReservedWord] ,
|
||||
# 78| 5: [Integer] 3
|
||||
# 78| 6: [ReservedWord] ]
|
||||
# 79| 14: [Assignment] Assignment
|
||||
# 79| 0: [Constant] A
|
||||
# 79| 1: [ReservedWord] =
|
||||
# 79| 2: [Array] Array
|
||||
# 79| 0: [ReservedWord] [
|
||||
# 79| 1: [Integer] 1
|
||||
# 79| 2: [ReservedWord] ,
|
||||
# 79| 3: [Integer] 2
|
||||
# 79| 4: [ReservedWord] ,
|
||||
# 79| 5: [Integer] 3
|
||||
# 79| 6: [ReservedWord] ]
|
||||
# 80| 15: [Assignment] Assignment
|
||||
# 80| 0: [Constant] B
|
||||
# 80| 1: [ReservedWord] =
|
||||
# 80| 2: [Identifier] a
|
||||
# 81| 16: [Assignment] Assignment
|
||||
# 81| 0: [Constant] C
|
||||
# 81| 1: [ReservedWord] =
|
||||
# 81| 2: [Constant] A
|
||||
# 82| 17: [Assignment] Assignment
|
||||
# 82| 0: [Identifier] b
|
||||
# 82| 1: [ReservedWord] =
|
||||
# 82| 2: [Constant] B
|
||||
# 84| 18: [If] If
|
||||
# 84| 0: [ReservedWord] if
|
||||
# 84| 1: [Identifier] condition
|
||||
# 84| 2: [Then] Then
|
||||
# 85| 0: [Assignment] Assignment
|
||||
# 85| 0: [Identifier] c
|
||||
# 85| 1: [ReservedWord] =
|
||||
# 85| 2: [Identifier] b
|
||||
# 86| 3: [ReservedWord] end
|
||||
# 87| 19: [Identifier] c
|
||||
# 26| [Comment] # A call to Kernel::Array; despite beginning with an upper-case character,
|
||||
# 27| [Comment] # we don't consider this to be a constant access.
|
||||
# 55| [Comment] # refers to ::ModuleA::FOURTY_FOUR
|
||||
# 57| [Comment] # refers to ::ModuleA::ModuleB::ClassB::FOURTY_FOUR
|
||||
# 76| [Comment] # Array constants
|
||||
# 87| [Comment] # not recognised
|
||||
control/cases.rb:
|
||||
# 1| [Program] Program
|
||||
# 2| 0: [Assignment] Assignment
|
||||
@@ -4558,6 +4604,17 @@ literals/literals.rb:
|
||||
# 193| cat file.txt
|
||||
# 193|
|
||||
# 195| 1: [HeredocEnd] SCRIPT
|
||||
misc/iso-8859-15.rb:
|
||||
# 1| [Program] Program
|
||||
# 4| 0: [Call] Call
|
||||
# 4| 0: [Identifier] print
|
||||
# 4| 1: [ArgumentList] ArgumentList
|
||||
# 4| 0: [String] String
|
||||
# 4| 0: [ReservedWord] "
|
||||
# 4| 1: [StringContent] EUR = €
|
||||
# 4| 2: [ReservedWord] "
|
||||
# 1| [Comment] #! /usr/bin/ruby
|
||||
# 2| [Comment] # coding: iso-8859-15
|
||||
misc/misc.erb:
|
||||
# 2| [Program] Program
|
||||
# 2| 0: [Call] Call
|
||||
|
||||
@@ -109,6 +109,12 @@ exprValue
|
||||
| constants/constants.rb:63:19:63:20 | 45 | 45 | int |
|
||||
| constants/constants.rb:65:19:65:35 | FOURTY_FIVE | 45 | int |
|
||||
| constants/constants.rb:71:18:71:19 | 46 | 46 | int |
|
||||
| constants/constants.rb:78:6:78:6 | 1 | 1 | int |
|
||||
| constants/constants.rb:78:9:78:9 | 2 | 2 | int |
|
||||
| constants/constants.rb:78:12:78:12 | 3 | 3 | int |
|
||||
| constants/constants.rb:79:6:79:6 | 1 | 1 | int |
|
||||
| constants/constants.rb:79:9:79:9 | 2 | 2 | int |
|
||||
| constants/constants.rb:79:12:79:12 | 3 | 3 | int |
|
||||
| control/cases.rb:2:5:2:5 | 0 | 0 | int |
|
||||
| control/cases.rb:3:5:3:5 | 0 | 0 | int |
|
||||
| control/cases.rb:4:5:4:5 | 0 | 0 | int |
|
||||
@@ -711,6 +717,7 @@ exprValue
|
||||
| literals/literals.rb:198:8:198:8 | 5 | 5 | int |
|
||||
| literals/literals.rb:199:2:199:2 | :y | :y | symbol |
|
||||
| literals/literals.rb:199:7:199:7 | :Z | :Z | symbol |
|
||||
| misc/iso-8859-15.rb:4:7:4:17 | "EUR = \u20ac" | EUR = \u20ac | string |
|
||||
| misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js | string |
|
||||
| misc/misc.rb:1:7:1:11 | "bar" | bar | string |
|
||||
| misc/misc.rb:3:7:3:9 | foo | foo | string |
|
||||
@@ -1004,6 +1011,12 @@ exprCfgNodeValue
|
||||
| constants/constants.rb:63:19:63:20 | 45 | 45 | int |
|
||||
| constants/constants.rb:65:19:65:35 | FOURTY_FIVE | 45 | int |
|
||||
| constants/constants.rb:71:18:71:19 | 46 | 46 | int |
|
||||
| constants/constants.rb:78:6:78:6 | 1 | 1 | int |
|
||||
| constants/constants.rb:78:9:78:9 | 2 | 2 | int |
|
||||
| constants/constants.rb:78:12:78:12 | 3 | 3 | int |
|
||||
| constants/constants.rb:79:6:79:6 | 1 | 1 | int |
|
||||
| constants/constants.rb:79:9:79:9 | 2 | 2 | int |
|
||||
| constants/constants.rb:79:12:79:12 | 3 | 3 | int |
|
||||
| control/cases.rb:2:5:2:5 | 0 | 0 | int |
|
||||
| control/cases.rb:3:5:3:5 | 0 | 0 | int |
|
||||
| control/cases.rb:4:5:4:5 | 0 | 0 | int |
|
||||
@@ -1580,6 +1593,7 @@ exprCfgNodeValue
|
||||
| literals/literals.rb:198:8:198:8 | 5 | 5 | int |
|
||||
| literals/literals.rb:199:2:199:2 | :y | :y | symbol |
|
||||
| literals/literals.rb:199:7:199:7 | :Z | :Z | symbol |
|
||||
| misc/iso-8859-15.rb:4:7:4:17 | "EUR = \u20ac" | EUR = \u20ac | string |
|
||||
| misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js | string |
|
||||
| misc/misc.rb:1:7:1:11 | "bar" | bar | string |
|
||||
| misc/misc.rb:3:7:3:9 | foo | foo | string |
|
||||
|
||||
@@ -61,6 +61,13 @@ constantAccess
|
||||
| constants.rb:71:5:71:14 | FOURTY_SIX | write | FOURTY_SIX | ConstantAssignment |
|
||||
| constants.rb:73:18:73:21 | Mod3 | read | Mod3 | ConstantReadAccess |
|
||||
| constants.rb:73:18:73:33 | FOURTY_SIX | read | FOURTY_SIX | ConstantReadAccess |
|
||||
| constants.rb:78:5:78:13 | Array | read | Array | ConstantReadAccess |
|
||||
| constants.rb:79:1:79:1 | A | write | A | ConstantAssignment |
|
||||
| constants.rb:79:5:79:13 | Array | read | Array | ConstantReadAccess |
|
||||
| constants.rb:80:1:80:1 | B | write | B | ConstantAssignment |
|
||||
| constants.rb:81:1:81:1 | C | write | C | ConstantAssignment |
|
||||
| constants.rb:81:5:81:5 | A | read | A | ConstantReadAccess |
|
||||
| constants.rb:82:5:82:5 | B | read | B | ConstantReadAccess |
|
||||
getConst
|
||||
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
|
||||
| constants.rb:1:1:15:3 | ModuleA | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
|
||||
@@ -71,23 +78,41 @@ getConst
|
||||
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_ONE | constants.rb:48:18:48:19 | 41 |
|
||||
| constants.rb:62:3:64:5 | Mod1::Mod3 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
|
||||
| constants.rb:70:3:72:5 | Mod1::Mod3::Mod5 | FOURTY_SIX | constants.rb:71:18:71:19 | 46 |
|
||||
| file://:0:0:0:0 | Object | A | constants.rb:79:5:79:13 | [...] |
|
||||
| file://:0:0:0:0 | Object | B | constants.rb:80:5:80:5 | a |
|
||||
| file://:0:0:0:0 | Object | C | constants.rb:81:5:81:5 | A |
|
||||
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
|
||||
lookupConst
|
||||
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
|
||||
| constants.rb:1:1:15:3 | ModuleA | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
|
||||
| constants.rb:2:5:4:7 | ModuleA::ClassA | A | constants.rb:79:5:79:13 | [...] |
|
||||
| constants.rb:2:5:4:7 | ModuleA::ClassA | B | constants.rb:80:5:80:5 | a |
|
||||
| constants.rb:2:5:4:7 | ModuleA::ClassA | C | constants.rb:81:5:81:5 | A |
|
||||
| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" |
|
||||
| constants.rb:2:5:4:7 | ModuleA::ClassA | GREETING | constants.rb:17:12:17:64 | ... + ... |
|
||||
| constants.rb:8:5:14:7 | ModuleA::ModuleB | MAX_SIZE | constants.rb:39:30:39:33 | 1024 |
|
||||
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | A | constants.rb:79:5:79:13 | [...] |
|
||||
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | B | constants.rb:80:5:80:5 | a |
|
||||
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | C | constants.rb:81:5:81:5 | A |
|
||||
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | GREETING | constants.rb:17:12:17:64 | ... + ... |
|
||||
| constants.rb:31:1:33:3 | ModuleA::ClassD | A | constants.rb:79:5:79:13 | [...] |
|
||||
| constants.rb:31:1:33:3 | ModuleA::ClassD | B | constants.rb:80:5:80:5 | a |
|
||||
| constants.rb:31:1:33:3 | ModuleA::ClassD | C | constants.rb:81:5:81:5 | A |
|
||||
| constants.rb:31:1:33:3 | ModuleA::ClassD | CONST_A | constants.rb:3:19:3:27 | "const_a" |
|
||||
| constants.rb:31:1:33:3 | ModuleA::ClassD | FOURTY_TWO | constants.rb:32:16:32:17 | 42 |
|
||||
| constants.rb:31:1:33:3 | ModuleA::ClassD | GREETING | constants.rb:17:12:17:64 | ... + ... |
|
||||
| constants.rb:35:1:37:3 | ModuleA::ModuleC | FOURTY_THREE | constants.rb:36:18:36:19 | 43 |
|
||||
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | A | constants.rb:79:5:79:13 | [...] |
|
||||
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | B | constants.rb:80:5:80:5 | a |
|
||||
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | C | constants.rb:81:5:81:5 | A |
|
||||
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_FOUR | constants.rb:56:19:56:20 | 44 |
|
||||
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_ONE | constants.rb:48:18:48:19 | 41 |
|
||||
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | GREETING | constants.rb:17:12:17:64 | ... + ... |
|
||||
| constants.rb:62:3:64:5 | Mod1::Mod3 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
|
||||
| constants.rb:70:3:72:5 | Mod1::Mod3::Mod5 | FOURTY_SIX | constants.rb:71:18:71:19 | 46 |
|
||||
| file://:0:0:0:0 | Object | A | constants.rb:79:5:79:13 | [...] |
|
||||
| file://:0:0:0:0 | Object | B | constants.rb:80:5:80:5 | a |
|
||||
| file://:0:0:0:0 | Object | C | constants.rb:81:5:81:5 | A |
|
||||
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
|
||||
constantValue
|
||||
| constants.rb:17:22:17:45 | CONST_A | constants.rb:3:19:3:27 | "const_a" |
|
||||
@@ -101,6 +126,8 @@ constantValue
|
||||
| constants.rb:57:21:57:31 | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
|
||||
| constants.rb:57:21:57:31 | FOURTY_FOUR | constants.rb:56:19:56:20 | 44 |
|
||||
| constants.rb:65:19:65:35 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
|
||||
| constants.rb:81:5:81:5 | A | constants.rb:79:5:79:13 | [...] |
|
||||
| constants.rb:82:5:82:5 | B | constants.rb:80:5:80:5 | a |
|
||||
constantWriteAccessQualifiedName
|
||||
| constants.rb:1:1:15:3 | ModuleA | ModuleA |
|
||||
| constants.rb:2:5:4:7 | ClassA | ModuleA::ClassA |
|
||||
@@ -133,3 +160,14 @@ constantWriteAccessQualifiedName
|
||||
| constants.rb:70:3:72:5 | Mod5 | Mod3::Mod5 |
|
||||
| constants.rb:71:5:71:14 | FOURTY_SIX | Mod1::Mod3::Mod5::FOURTY_SIX |
|
||||
| constants.rb:71:5:71:14 | FOURTY_SIX | Mod3::Mod5::FOURTY_SIX |
|
||||
| constants.rb:79:1:79:1 | A | A |
|
||||
| constants.rb:80:1:80:1 | B | B |
|
||||
| constants.rb:81:1:81:1 | C | C |
|
||||
arrayConstant
|
||||
| constants.rb:20:13:20:37 | call to [] | constants.rb:20:13:20:37 | call to [] |
|
||||
| constants.rb:78:5:78:13 | call to [] | constants.rb:78:5:78:13 | call to [] |
|
||||
| constants.rb:79:5:79:13 | call to [] | constants.rb:79:5:79:13 | call to [] |
|
||||
| constants.rb:80:5:80:5 | a | constants.rb:78:5:78:13 | call to [] |
|
||||
| constants.rb:81:5:81:5 | A | constants.rb:79:5:79:13 | call to [] |
|
||||
| constants.rb:82:5:82:5 | B | constants.rb:78:5:78:13 | call to [] |
|
||||
| constants.rb:85:7:85:7 | b | constants.rb:78:5:78:13 | call to [] |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ruby
|
||||
import codeql.ruby.ast.internal.Module as M
|
||||
import codeql.ruby.ast.internal.Constant
|
||||
|
||||
query predicate constantAccess(ConstantAccess a, string kind, string name, string cls) {
|
||||
(
|
||||
@@ -20,3 +21,5 @@ query predicate constantValue(ConstantReadAccess a, Expr e) { e = a.getValue() }
|
||||
query predicate constantWriteAccessQualifiedName(ConstantWriteAccess w, string qualifiedName) {
|
||||
w.getAQualifiedName() = qualifiedName
|
||||
}
|
||||
|
||||
query predicate arrayConstant = isArrayConstant/2;
|
||||
|
||||
@@ -72,3 +72,16 @@ module Mod4
|
||||
end
|
||||
@@fourty_six = Mod3::FOURTY_SIX
|
||||
end
|
||||
|
||||
# Array constants
|
||||
|
||||
a = [1, 2, 3]
|
||||
A = [1, 2, 3]
|
||||
B = a
|
||||
C = A
|
||||
b = B
|
||||
|
||||
if condition
|
||||
c = b
|
||||
end
|
||||
c # not recognised
|
||||
|
||||
4
ruby/ql/test/library-tests/ast/misc/iso-8859-15.rb
Normal file
4
ruby/ql/test/library-tests/ast/misc/iso-8859-15.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
#! /usr/bin/ruby
|
||||
# coding: iso-8859-15
|
||||
|
||||
print "EUR = <20>"
|
||||
@@ -14,6 +14,12 @@
|
||||
| app/controllers/users_controller.rb:20:7:20:57 | call to update_attributes | app/controllers/users_controller.rb:20:49:20:55 | call to get_uid |
|
||||
| app/controllers/users_controller.rb:23:7:23:42 | call to update_attribute | app/controllers/users_controller.rb:23:37:23:41 | "U13" |
|
||||
| app/controllers/users_controller.rb:26:19:26:23 | ... = ... | app/controllers/users_controller.rb:26:19:26:23 | "U14" |
|
||||
| app/controllers/users_controller.rb:31:7:31:32 | call to touch_all | app/controllers/users_controller.rb:31:28:31:31 | call to time |
|
||||
| app/controllers/users_controller.rb:35:7:35:27 | call to update | app/controllers/users_controller.rb:35:22:35:26 | attrs |
|
||||
| app/controllers/users_controller.rb:36:7:36:28 | call to update! | app/controllers/users_controller.rb:36:23:36:27 | attrs |
|
||||
| app/controllers/users_controller.rb:39:7:39:24 | call to create | app/controllers/users_controller.rb:39:19:39:23 | attrs |
|
||||
| app/controllers/users_controller.rb:40:7:40:25 | call to create! | app/controllers/users_controller.rb:40:20:40:24 | attrs |
|
||||
| app/controllers/users_controller.rb:41:7:41:24 | call to insert | app/controllers/users_controller.rb:41:19:41:23 | attrs |
|
||||
| app/models/user.rb:4:5:4:28 | call to update | app/models/user.rb:4:23:4:27 | "U15" |
|
||||
| app/models/user.rb:5:5:5:23 | call to update | app/models/user.rb:5:18:5:22 | "U16" |
|
||||
| app/models/user.rb:6:5:6:56 | call to update_attributes | app/models/user.rb:6:35:6:39 | "U17" |
|
||||
|
||||
@@ -25,6 +25,20 @@ module Users
|
||||
# AssignAttributeCall
|
||||
user.name = "U14"
|
||||
user.save
|
||||
|
||||
# TouchAllCall
|
||||
User.touch_all
|
||||
User.touch_all(time: time)
|
||||
|
||||
# UpdateLikeClassMethodCall
|
||||
attrs = {name: "U15"}
|
||||
User.update(8, attrs)
|
||||
User.update!(8, attrs)
|
||||
|
||||
# CreateLikeClassMethodCall
|
||||
User.create(attrs)
|
||||
User.create!(attrs)
|
||||
User.insert(attrs)
|
||||
end
|
||||
|
||||
def get_uid
|
||||
|
||||
@@ -26,10 +26,10 @@ class ApiUseTest extends InlineExpectationsTest {
|
||||
l = n.getLocation() and
|
||||
(
|
||||
tag = "use" and
|
||||
n = a.getAUse()
|
||||
n = a.getAValueReachableFromSource()
|
||||
or
|
||||
tag = "def" and
|
||||
n = a.getARhs()
|
||||
n = a.asSink()
|
||||
or
|
||||
tag = "call" and
|
||||
n = a.(API::MethodAccessNode).getCallNode()
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
WARNING: Type BarrierGuard has been deprecated and may be removed in future (barrier-guards.ql:8,3-15)
|
||||
oldStyleBarrierGuards
|
||||
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | barrier-guards.rb:3:4:3:6 | foo | true |
|
||||
| barrier-guards.rb:9:4:9:24 | call to include? | barrier-guards.rb:10:5:10:7 | foo | barrier-guards.rb:9:21:9:23 | foo | true |
|
||||
| barrier-guards.rb:15:4:15:15 | ... != ... | barrier-guards.rb:18:5:18:7 | foo | barrier-guards.rb:15:4:15:6 | foo | false |
|
||||
| barrier-guards.rb:21:8:21:19 | ... == ... | barrier-guards.rb:24:5:24:7 | foo | barrier-guards.rb:21:8:21:10 | foo | true |
|
||||
| barrier-guards.rb:27:8:27:19 | ... != ... | barrier-guards.rb:28:5:28:7 | foo | barrier-guards.rb:27:8:27:10 | foo | false |
|
||||
| barrier-guards.rb:37:4:37:20 | call to include? | barrier-guards.rb:38:5:38:7 | foo | barrier-guards.rb:37:17:37:19 | foo | true |
|
||||
| barrier-guards.rb:43:4:43:15 | ... == ... | barrier-guards.rb:45:9:45:11 | foo | barrier-guards.rb:43:4:43:6 | foo | true |
|
||||
| barrier-guards.rb:70:4:70:21 | call to include? | barrier-guards.rb:71:5:71:7 | foo | barrier-guards.rb:70:18:70:20 | foo | true |
|
||||
| barrier-guards.rb:82:4:82:25 | ... != ... | barrier-guards.rb:83:5:83:7 | foo | barrier-guards.rb:82:15:82:17 | foo | true |
|
||||
newStyleBarrierGuards
|
||||
| barrier-guards.rb:4:5:4:7 | foo |
|
||||
| barrier-guards.rb:10:5:10:7 | foo |
|
||||
| barrier-guards.rb:18:5:18:7 | foo |
|
||||
| barrier-guards.rb:24:5:24:7 | foo |
|
||||
| barrier-guards.rb:28:5:28:7 | foo |
|
||||
| barrier-guards.rb:38:5:38:7 | foo |
|
||||
| barrier-guards.rb:45:9:45:11 | foo |
|
||||
| barrier-guards.rb:71:5:71:7 | foo |
|
||||
| barrier-guards.rb:83:5:83:7 | foo |
|
||||
| barrier-guards.rb:91:5:91:7 | foo |
|
||||
@@ -0,0 +1,16 @@
|
||||
import codeql.ruby.dataflow.internal.DataFlowPublic
|
||||
import codeql.ruby.dataflow.BarrierGuards
|
||||
import codeql.ruby.controlflow.CfgNodes
|
||||
import codeql.ruby.controlflow.ControlFlowGraph
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
query predicate oldStyleBarrierGuards(
|
||||
BarrierGuard g, DataFlow::Node guardedNode, ExprCfgNode expr, boolean branch
|
||||
) {
|
||||
g.checks(expr, branch) and guardedNode = g.getAGuardedNode()
|
||||
}
|
||||
|
||||
query predicate newStyleBarrierGuards(DataFlow::Node n) {
|
||||
n instanceof StringConstCompareBarrier or
|
||||
n instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
foo = "foo"
|
||||
|
||||
if foo == "foo"
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
if ["foo"].include?(foo)
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
if foo != "foo"
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
unless foo == "foo"
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
unless foo != "foo"
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
foo
|
||||
|
||||
FOO = ["foo"]
|
||||
|
||||
if FOO.include?(foo)
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
if foo == "foo"
|
||||
capture {
|
||||
foo # guarded
|
||||
}
|
||||
end
|
||||
|
||||
if foo == "foo"
|
||||
capture {
|
||||
foo = "bar"
|
||||
foo # not guarded
|
||||
}
|
||||
end
|
||||
|
||||
if foo == "foo"
|
||||
my_lambda = -> () {
|
||||
foo # not guarded
|
||||
}
|
||||
|
||||
foo = "bar"
|
||||
|
||||
my_lambda()
|
||||
end
|
||||
|
||||
foos = nil
|
||||
foos = ["foo"]
|
||||
bars = NotAnArray.new
|
||||
|
||||
if foos.include?(foo)
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
if bars.include?(foo)
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
if foos.index(foo) != nil
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
if foos.index(foo)r == nil
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
bars = ["bar"]
|
||||
|
||||
if condition
|
||||
bars = nil
|
||||
end
|
||||
|
||||
if bars.include?(foo)
|
||||
foo
|
||||
else
|
||||
foo
|
||||
end
|
||||
@@ -26,6 +26,16 @@ edges
|
||||
| params_flow.rb:35:12:35:20 | call to taint : | params_flow.rb:25:12:25:13 | p1 : |
|
||||
| params_flow.rb:35:23:35:28 | ** ... [element :p3] : | params_flow.rb:25:17:25:24 | **kwargs [element :p3] : |
|
||||
| params_flow.rb:35:25:35:28 | args [element :p3] : | params_flow.rb:35:23:35:28 | ** ... [element :p3] : |
|
||||
| params_flow.rb:37:16:37:24 | call to taint : | params_flow.rb:38:10:38:13 | args [element :p1] : |
|
||||
| params_flow.rb:37:34:37:42 | call to taint : | params_flow.rb:38:10:38:13 | args [element :p2] : |
|
||||
| params_flow.rb:38:8:38:13 | ** ... [element :p1] : | params_flow.rb:25:12:25:13 | p1 : |
|
||||
| params_flow.rb:38:8:38:13 | ** ... [element :p2] : | params_flow.rb:25:17:25:24 | **kwargs [element :p2] : |
|
||||
| params_flow.rb:38:10:38:13 | args [element :p1] : | params_flow.rb:38:8:38:13 | ** ... [element :p1] : |
|
||||
| params_flow.rb:38:10:38:13 | args [element :p2] : | params_flow.rb:38:8:38:13 | ** ... [element :p2] : |
|
||||
| params_flow.rb:40:16:40:24 | call to taint : | params_flow.rb:41:26:41:29 | args [element :p1] : |
|
||||
| params_flow.rb:41:13:41:21 | call to taint : | params_flow.rb:16:18:16:19 | p2 : |
|
||||
| params_flow.rb:41:24:41:29 | ** ... [element :p1] : | params_flow.rb:16:13:16:14 | p1 : |
|
||||
| params_flow.rb:41:26:41:29 | args [element :p1] : | params_flow.rb:41:24:41:29 | ** ... [element :p1] : |
|
||||
nodes
|
||||
| params_flow.rb:9:16:9:17 | p1 : | semmle.label | p1 : |
|
||||
| params_flow.rb:9:20:9:21 | p2 : | semmle.label | p2 : |
|
||||
@@ -60,6 +70,16 @@ nodes
|
||||
| params_flow.rb:35:12:35:20 | call to taint : | semmle.label | call to taint : |
|
||||
| params_flow.rb:35:23:35:28 | ** ... [element :p3] : | semmle.label | ** ... [element :p3] : |
|
||||
| params_flow.rb:35:25:35:28 | args [element :p3] : | semmle.label | args [element :p3] : |
|
||||
| params_flow.rb:37:16:37:24 | call to taint : | semmle.label | call to taint : |
|
||||
| params_flow.rb:37:34:37:42 | call to taint : | semmle.label | call to taint : |
|
||||
| params_flow.rb:38:8:38:13 | ** ... [element :p1] : | semmle.label | ** ... [element :p1] : |
|
||||
| params_flow.rb:38:8:38:13 | ** ... [element :p2] : | semmle.label | ** ... [element :p2] : |
|
||||
| params_flow.rb:38:10:38:13 | args [element :p1] : | semmle.label | args [element :p1] : |
|
||||
| params_flow.rb:38:10:38:13 | args [element :p2] : | semmle.label | args [element :p2] : |
|
||||
| params_flow.rb:40:16:40:24 | call to taint : | semmle.label | call to taint : |
|
||||
| params_flow.rb:41:13:41:21 | call to taint : | semmle.label | call to taint : |
|
||||
| params_flow.rb:41:24:41:29 | ** ... [element :p1] : | semmle.label | ** ... [element :p1] : |
|
||||
| params_flow.rb:41:26:41:29 | args [element :p1] : | semmle.label | args [element :p1] : |
|
||||
subpaths
|
||||
#select
|
||||
| params_flow.rb:10:10:10:11 | p1 | params_flow.rb:14:12:14:19 | call to taint : | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:14:12:14:19 | call to taint : | call to taint : |
|
||||
@@ -67,11 +87,15 @@ subpaths
|
||||
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:21:13:21:20 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:21:13:21:20 | call to taint : | call to taint : |
|
||||
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:22:27:22:34 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:22:27:22:34 | call to taint : | call to taint : |
|
||||
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:23:33:23:40 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:23:33:23:40 | call to taint : | call to taint : |
|
||||
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:40:16:40:24 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:40:16:40:24 | call to taint : | call to taint : |
|
||||
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:21:27:21:34 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:21:27:21:34 | call to taint : | call to taint : |
|
||||
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:22:13:22:20 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:22:13:22:20 | call to taint : | call to taint : |
|
||||
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:23:16:23:23 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:23:16:23:23 | call to taint : | call to taint : |
|
||||
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:41:13:41:21 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:41:13:41:21 | call to taint : | call to taint : |
|
||||
| params_flow.rb:26:10:26:11 | p1 | params_flow.rb:33:12:33:19 | call to taint : | params_flow.rb:26:10:26:11 | p1 | $@ | params_flow.rb:33:12:33:19 | call to taint : | call to taint : |
|
||||
| params_flow.rb:26:10:26:11 | p1 | params_flow.rb:35:12:35:20 | call to taint : | params_flow.rb:26:10:26:11 | p1 | $@ | params_flow.rb:35:12:35:20 | call to taint : | call to taint : |
|
||||
| params_flow.rb:26:10:26:11 | p1 | params_flow.rb:37:16:37:24 | call to taint : | params_flow.rb:26:10:26:11 | p1 | $@ | params_flow.rb:37:16:37:24 | call to taint : | call to taint : |
|
||||
| params_flow.rb:28:10:28:22 | ( ... ) | params_flow.rb:33:26:33:34 | call to taint : | params_flow.rb:28:10:28:22 | ( ... ) | $@ | params_flow.rb:33:26:33:34 | call to taint : | call to taint : |
|
||||
| params_flow.rb:28:10:28:22 | ( ... ) | params_flow.rb:37:34:37:42 | call to taint : | params_flow.rb:28:10:28:22 | ( ... ) | $@ | params_flow.rb:37:34:37:42 | call to taint : | call to taint : |
|
||||
| params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:33:41:33:49 | call to taint : | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:33:41:33:49 | call to taint : | call to taint : |
|
||||
| params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:34:14:34:22 | call to taint : | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:34:14:34:22 | call to taint : | call to taint : |
|
||||
|
||||
@@ -14,8 +14,8 @@ end
|
||||
positional(taint(1), taint(2))
|
||||
|
||||
def keyword(p1:, p2:)
|
||||
sink p1 # $ hasValueFlow=3 $ hasValueFlow=6 $ hasValueFlow=8
|
||||
sink p2 # $ hasValueFlow=4 $ hasValueFlow=5 $ hasValueFlow=7
|
||||
sink p1 # $ hasValueFlow=3 $ hasValueFlow=6 $ hasValueFlow=8 $ hasValueFlow=16
|
||||
sink p2 # $ hasValueFlow=4 $ hasValueFlow=5 $ hasValueFlow=7 $ hasValueFlow=17
|
||||
end
|
||||
|
||||
keyword(p1: taint(3), p2: taint(4))
|
||||
@@ -23,9 +23,9 @@ keyword(p2: taint(5), p1: taint(6))
|
||||
keyword(:p2 => taint(7), :p1 => taint(8))
|
||||
|
||||
def kwargs(p1:, **kwargs)
|
||||
sink p1 # $ hasValueFlow=9 $ hasValueFlow=13
|
||||
sink p1 # $ hasValueFlow=9 $ hasValueFlow=13 $ hasValueFlow=14
|
||||
sink (kwargs[:p1])
|
||||
sink (kwargs[:p2]) # $ hasValueFlow=10
|
||||
sink (kwargs[:p2]) # $ hasValueFlow=10 $ hasValueFlow=15
|
||||
sink (kwargs[:p3]) # $ hasValueFlow=11 $ hasValueFlow=12
|
||||
sink (kwargs[:p4])
|
||||
end
|
||||
@@ -33,3 +33,9 @@ end
|
||||
kwargs(p1: taint(9), p2: taint(10), p3: taint(11), p4: "")
|
||||
args = { p3: taint(12), p4: "" }
|
||||
kwargs(p1: taint(13), **args)
|
||||
|
||||
args = {:p1 => taint(14), :p2 => taint(15) }
|
||||
kwargs(**args)
|
||||
|
||||
args = {:p1 => taint(16) }
|
||||
keyword(p2: taint(17), **args)
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
failures
|
||||
edges
|
||||
| pathname_flow.rb:4:10:4:33 | call to new : | pathname_flow.rb:5:10:5:11 | pn |
|
||||
| pathname_flow.rb:4:23:4:32 | call to source : | pathname_flow.rb:4:10:4:33 | call to new : |
|
||||
| pathname_flow.rb:9:7:9:30 | call to new : | pathname_flow.rb:11:8:11:12 | ... + ... |
|
||||
| pathname_flow.rb:9:20:9:29 | call to source : | pathname_flow.rb:9:7:9:30 | call to new : |
|
||||
| pathname_flow.rb:10:7:10:30 | call to new : | pathname_flow.rb:11:8:11:12 | ... + ... |
|
||||
| pathname_flow.rb:10:20:10:29 | call to source : | pathname_flow.rb:10:7:10:30 | call to new : |
|
||||
| pathname_flow.rb:15:8:15:31 | call to new : | pathname_flow.rb:16:8:16:9 | pn : |
|
||||
| pathname_flow.rb:15:21:15:30 | call to source : | pathname_flow.rb:15:8:15:31 | call to new : |
|
||||
| pathname_flow.rb:16:8:16:9 | pn : | pathname_flow.rb:16:8:16:17 | call to dirname |
|
||||
| pathname_flow.rb:20:7:20:30 | call to new : | pathname_flow.rb:21:3:21:3 | a : |
|
||||
| pathname_flow.rb:20:20:20:29 | call to source : | pathname_flow.rb:20:7:20:30 | call to new : |
|
||||
| pathname_flow.rb:21:3:21:3 | a : | pathname_flow.rb:21:23:21:23 | x : |
|
||||
| pathname_flow.rb:21:23:21:23 | x : | pathname_flow.rb:22:10:22:10 | x |
|
||||
| pathname_flow.rb:27:7:27:30 | call to new : | pathname_flow.rb:28:8:28:8 | a : |
|
||||
| pathname_flow.rb:27:20:27:29 | call to source : | pathname_flow.rb:27:7:27:30 | call to new : |
|
||||
| pathname_flow.rb:28:8:28:8 | a : | pathname_flow.rb:28:8:28:22 | call to expand_path |
|
||||
| pathname_flow.rb:32:7:32:30 | call to new : | pathname_flow.rb:35:8:35:8 | a : |
|
||||
| pathname_flow.rb:32:20:32:29 | call to source : | pathname_flow.rb:32:7:32:30 | call to new : |
|
||||
| pathname_flow.rb:34:7:34:30 | call to new : | pathname_flow.rb:35:18:35:18 | c : |
|
||||
| pathname_flow.rb:34:20:34:29 | call to source : | pathname_flow.rb:34:7:34:30 | call to new : |
|
||||
| pathname_flow.rb:35:8:35:8 | a : | pathname_flow.rb:35:8:35:19 | call to join |
|
||||
| pathname_flow.rb:35:18:35:18 | c : | pathname_flow.rb:35:8:35:19 | call to join |
|
||||
| pathname_flow.rb:39:7:39:30 | call to new : | pathname_flow.rb:40:8:40:8 | a : |
|
||||
| pathname_flow.rb:39:20:39:29 | call to source : | pathname_flow.rb:39:7:39:30 | call to new : |
|
||||
| pathname_flow.rb:40:8:40:8 | a : | pathname_flow.rb:40:8:40:17 | call to parent |
|
||||
| pathname_flow.rb:44:7:44:30 | call to new : | pathname_flow.rb:45:8:45:8 | a : |
|
||||
| pathname_flow.rb:44:20:44:29 | call to source : | pathname_flow.rb:44:7:44:30 | call to new : |
|
||||
| pathname_flow.rb:45:8:45:8 | a : | pathname_flow.rb:45:8:45:19 | call to realpath |
|
||||
| pathname_flow.rb:49:7:49:30 | call to new : | pathname_flow.rb:50:8:50:8 | a : |
|
||||
| pathname_flow.rb:49:20:49:29 | call to source : | pathname_flow.rb:49:7:49:30 | call to new : |
|
||||
| pathname_flow.rb:50:8:50:8 | a : | pathname_flow.rb:50:8:50:39 | call to relative_path_from |
|
||||
| pathname_flow.rb:54:7:54:30 | call to new : | pathname_flow.rb:55:8:55:8 | a : |
|
||||
| pathname_flow.rb:54:20:54:29 | call to source : | pathname_flow.rb:54:7:54:30 | call to new : |
|
||||
| pathname_flow.rb:55:8:55:8 | a : | pathname_flow.rb:55:8:55:16 | call to to_path |
|
||||
| pathname_flow.rb:59:7:59:30 | call to new : | pathname_flow.rb:60:8:60:8 | a : |
|
||||
| pathname_flow.rb:59:20:59:29 | call to source : | pathname_flow.rb:59:7:59:30 | call to new : |
|
||||
| pathname_flow.rb:60:8:60:8 | a : | pathname_flow.rb:60:8:60:13 | call to to_s |
|
||||
| pathname_flow.rb:64:7:64:30 | call to new : | pathname_flow.rb:66:8:66:8 | b |
|
||||
| pathname_flow.rb:64:20:64:29 | call to source : | pathname_flow.rb:64:7:64:30 | call to new : |
|
||||
| pathname_flow.rb:70:7:70:30 | call to new : | pathname_flow.rb:72:8:72:8 | b |
|
||||
| pathname_flow.rb:70:20:70:29 | call to source : | pathname_flow.rb:70:7:70:30 | call to new : |
|
||||
| pathname_flow.rb:76:7:76:30 | call to new : | pathname_flow.rb:77:7:77:7 | a : |
|
||||
| pathname_flow.rb:76:20:76:29 | call to source : | pathname_flow.rb:76:7:76:30 | call to new : |
|
||||
| pathname_flow.rb:77:7:77:7 | a : | pathname_flow.rb:77:7:77:16 | call to basename : |
|
||||
| pathname_flow.rb:77:7:77:16 | call to basename : | pathname_flow.rb:78:8:78:8 | b |
|
||||
| pathname_flow.rb:82:7:82:30 | call to new : | pathname_flow.rb:83:7:83:7 | a : |
|
||||
| pathname_flow.rb:82:20:82:29 | call to source : | pathname_flow.rb:82:7:82:30 | call to new : |
|
||||
| pathname_flow.rb:83:7:83:7 | a : | pathname_flow.rb:83:7:83:17 | call to cleanpath : |
|
||||
| pathname_flow.rb:83:7:83:17 | call to cleanpath : | pathname_flow.rb:84:8:84:8 | b |
|
||||
| pathname_flow.rb:88:7:88:30 | call to new : | pathname_flow.rb:89:7:89:7 | a : |
|
||||
| pathname_flow.rb:88:20:88:29 | call to source : | pathname_flow.rb:88:7:88:30 | call to new : |
|
||||
| pathname_flow.rb:89:7:89:7 | a : | pathname_flow.rb:89:7:89:25 | call to sub : |
|
||||
| pathname_flow.rb:89:7:89:25 | call to sub : | pathname_flow.rb:90:8:90:8 | b |
|
||||
| pathname_flow.rb:94:7:94:30 | call to new : | pathname_flow.rb:95:7:95:7 | a : |
|
||||
| pathname_flow.rb:94:20:94:29 | call to source : | pathname_flow.rb:94:7:94:30 | call to new : |
|
||||
| pathname_flow.rb:95:7:95:7 | a : | pathname_flow.rb:95:7:95:23 | call to sub_ext : |
|
||||
| pathname_flow.rb:95:7:95:23 | call to sub_ext : | pathname_flow.rb:96:8:96:8 | b |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:104:8:104:8 | b : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:107:8:107:8 | c : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:109:7:109:7 | a : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:112:7:112:7 | a : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:115:7:115:7 | a : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:118:7:118:7 | a : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:121:7:121:7 | a : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:124:7:124:7 | a : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:127:7:127:7 | a : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:130:7:130:7 | a : |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:133:7:133:7 | a : |
|
||||
| pathname_flow.rb:101:20:101:29 | call to source : | pathname_flow.rb:101:7:101:30 | call to new : |
|
||||
| pathname_flow.rb:104:8:104:8 | b : | pathname_flow.rb:104:8:104:17 | call to realpath |
|
||||
| pathname_flow.rb:107:8:107:8 | c : | pathname_flow.rb:107:8:107:17 | call to realpath |
|
||||
| pathname_flow.rb:109:7:109:7 | a : | pathname_flow.rb:109:7:109:16 | call to basename : |
|
||||
| pathname_flow.rb:109:7:109:16 | call to basename : | pathname_flow.rb:110:8:110:8 | d : |
|
||||
| pathname_flow.rb:110:8:110:8 | d : | pathname_flow.rb:110:8:110:17 | call to realpath |
|
||||
| pathname_flow.rb:112:7:112:7 | a : | pathname_flow.rb:112:7:112:17 | call to cleanpath : |
|
||||
| pathname_flow.rb:112:7:112:17 | call to cleanpath : | pathname_flow.rb:113:8:113:8 | e : |
|
||||
| pathname_flow.rb:113:8:113:8 | e : | pathname_flow.rb:113:8:113:17 | call to realpath |
|
||||
| pathname_flow.rb:115:7:115:7 | a : | pathname_flow.rb:115:7:115:19 | call to expand_path : |
|
||||
| pathname_flow.rb:115:7:115:19 | call to expand_path : | pathname_flow.rb:116:8:116:8 | f : |
|
||||
| pathname_flow.rb:116:8:116:8 | f : | pathname_flow.rb:116:8:116:17 | call to realpath |
|
||||
| pathname_flow.rb:118:7:118:7 | a : | pathname_flow.rb:118:7:118:19 | call to join : |
|
||||
| pathname_flow.rb:118:7:118:19 | call to join : | pathname_flow.rb:119:8:119:8 | g : |
|
||||
| pathname_flow.rb:119:8:119:8 | g : | pathname_flow.rb:119:8:119:17 | call to realpath |
|
||||
| pathname_flow.rb:121:7:121:7 | a : | pathname_flow.rb:121:7:121:16 | call to realpath : |
|
||||
| pathname_flow.rb:121:7:121:16 | call to realpath : | pathname_flow.rb:122:8:122:8 | h : |
|
||||
| pathname_flow.rb:122:8:122:8 | h : | pathname_flow.rb:122:8:122:17 | call to realpath |
|
||||
| pathname_flow.rb:124:7:124:7 | a : | pathname_flow.rb:124:7:124:38 | call to relative_path_from : |
|
||||
| pathname_flow.rb:124:7:124:38 | call to relative_path_from : | pathname_flow.rb:125:8:125:8 | i : |
|
||||
| pathname_flow.rb:125:8:125:8 | i : | pathname_flow.rb:125:8:125:17 | call to realpath |
|
||||
| pathname_flow.rb:127:7:127:7 | a : | pathname_flow.rb:127:7:127:25 | call to sub : |
|
||||
| pathname_flow.rb:127:7:127:25 | call to sub : | pathname_flow.rb:128:8:128:8 | j : |
|
||||
| pathname_flow.rb:128:8:128:8 | j : | pathname_flow.rb:128:8:128:17 | call to realpath |
|
||||
| pathname_flow.rb:130:7:130:7 | a : | pathname_flow.rb:130:7:130:23 | call to sub_ext : |
|
||||
| pathname_flow.rb:130:7:130:23 | call to sub_ext : | pathname_flow.rb:131:8:131:8 | k : |
|
||||
| pathname_flow.rb:131:8:131:8 | k : | pathname_flow.rb:131:8:131:17 | call to realpath |
|
||||
| pathname_flow.rb:133:7:133:7 | a : | pathname_flow.rb:133:7:133:15 | call to to_path : |
|
||||
| pathname_flow.rb:133:7:133:15 | call to to_path : | pathname_flow.rb:134:8:134:8 | l : |
|
||||
| pathname_flow.rb:134:8:134:8 | l : | pathname_flow.rb:134:8:134:17 | call to realpath |
|
||||
nodes
|
||||
| pathname_flow.rb:4:10:4:33 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:4:23:4:32 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:5:10:5:11 | pn | semmle.label | pn |
|
||||
| pathname_flow.rb:9:7:9:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:9:20:9:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:10:7:10:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:10:20:10:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:11:8:11:12 | ... + ... | semmle.label | ... + ... |
|
||||
| pathname_flow.rb:15:8:15:31 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:15:21:15:30 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:16:8:16:9 | pn : | semmle.label | pn : |
|
||||
| pathname_flow.rb:16:8:16:17 | call to dirname | semmle.label | call to dirname |
|
||||
| pathname_flow.rb:20:7:20:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:20:20:20:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:21:3:21:3 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:21:23:21:23 | x : | semmle.label | x : |
|
||||
| pathname_flow.rb:22:10:22:10 | x | semmle.label | x |
|
||||
| pathname_flow.rb:27:7:27:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:27:20:27:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:28:8:28:8 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:28:8:28:22 | call to expand_path | semmle.label | call to expand_path |
|
||||
| pathname_flow.rb:32:7:32:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:32:20:32:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:34:7:34:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:34:20:34:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:35:8:35:8 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:35:8:35:19 | call to join | semmle.label | call to join |
|
||||
| pathname_flow.rb:35:18:35:18 | c : | semmle.label | c : |
|
||||
| pathname_flow.rb:39:7:39:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:39:20:39:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:40:8:40:8 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:40:8:40:17 | call to parent | semmle.label | call to parent |
|
||||
| pathname_flow.rb:44:7:44:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:44:20:44:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:45:8:45:8 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:45:8:45:19 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:49:7:49:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:49:20:49:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:50:8:50:8 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:50:8:50:39 | call to relative_path_from | semmle.label | call to relative_path_from |
|
||||
| pathname_flow.rb:54:7:54:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:54:20:54:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:55:8:55:8 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:55:8:55:16 | call to to_path | semmle.label | call to to_path |
|
||||
| pathname_flow.rb:59:7:59:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:59:20:59:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:60:8:60:8 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:60:8:60:13 | call to to_s | semmle.label | call to to_s |
|
||||
| pathname_flow.rb:64:7:64:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:64:20:64:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:66:8:66:8 | b | semmle.label | b |
|
||||
| pathname_flow.rb:70:7:70:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:70:20:70:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:72:8:72:8 | b | semmle.label | b |
|
||||
| pathname_flow.rb:76:7:76:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:76:20:76:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:77:7:77:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:77:7:77:16 | call to basename : | semmle.label | call to basename : |
|
||||
| pathname_flow.rb:78:8:78:8 | b | semmle.label | b |
|
||||
| pathname_flow.rb:82:7:82:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:82:20:82:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:83:7:83:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:83:7:83:17 | call to cleanpath : | semmle.label | call to cleanpath : |
|
||||
| pathname_flow.rb:84:8:84:8 | b | semmle.label | b |
|
||||
| pathname_flow.rb:88:7:88:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:88:20:88:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:89:7:89:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:89:7:89:25 | call to sub : | semmle.label | call to sub : |
|
||||
| pathname_flow.rb:90:8:90:8 | b | semmle.label | b |
|
||||
| pathname_flow.rb:94:7:94:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:94:20:94:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:95:7:95:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:95:7:95:23 | call to sub_ext : | semmle.label | call to sub_ext : |
|
||||
| pathname_flow.rb:96:8:96:8 | b | semmle.label | b |
|
||||
| pathname_flow.rb:101:7:101:30 | call to new : | semmle.label | call to new : |
|
||||
| pathname_flow.rb:101:20:101:29 | call to source : | semmle.label | call to source : |
|
||||
| pathname_flow.rb:104:8:104:8 | b : | semmle.label | b : |
|
||||
| pathname_flow.rb:104:8:104:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:107:8:107:8 | c : | semmle.label | c : |
|
||||
| pathname_flow.rb:107:8:107:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:109:7:109:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:109:7:109:16 | call to basename : | semmle.label | call to basename : |
|
||||
| pathname_flow.rb:110:8:110:8 | d : | semmle.label | d : |
|
||||
| pathname_flow.rb:110:8:110:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:112:7:112:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:112:7:112:17 | call to cleanpath : | semmle.label | call to cleanpath : |
|
||||
| pathname_flow.rb:113:8:113:8 | e : | semmle.label | e : |
|
||||
| pathname_flow.rb:113:8:113:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:115:7:115:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:115:7:115:19 | call to expand_path : | semmle.label | call to expand_path : |
|
||||
| pathname_flow.rb:116:8:116:8 | f : | semmle.label | f : |
|
||||
| pathname_flow.rb:116:8:116:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:118:7:118:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:118:7:118:19 | call to join : | semmle.label | call to join : |
|
||||
| pathname_flow.rb:119:8:119:8 | g : | semmle.label | g : |
|
||||
| pathname_flow.rb:119:8:119:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:121:7:121:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:121:7:121:16 | call to realpath : | semmle.label | call to realpath : |
|
||||
| pathname_flow.rb:122:8:122:8 | h : | semmle.label | h : |
|
||||
| pathname_flow.rb:122:8:122:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:124:7:124:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:124:7:124:38 | call to relative_path_from : | semmle.label | call to relative_path_from : |
|
||||
| pathname_flow.rb:125:8:125:8 | i : | semmle.label | i : |
|
||||
| pathname_flow.rb:125:8:125:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:127:7:127:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:127:7:127:25 | call to sub : | semmle.label | call to sub : |
|
||||
| pathname_flow.rb:128:8:128:8 | j : | semmle.label | j : |
|
||||
| pathname_flow.rb:128:8:128:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:130:7:130:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:130:7:130:23 | call to sub_ext : | semmle.label | call to sub_ext : |
|
||||
| pathname_flow.rb:131:8:131:8 | k : | semmle.label | k : |
|
||||
| pathname_flow.rb:131:8:131:17 | call to realpath | semmle.label | call to realpath |
|
||||
| pathname_flow.rb:133:7:133:7 | a : | semmle.label | a : |
|
||||
| pathname_flow.rb:133:7:133:15 | call to to_path : | semmle.label | call to to_path : |
|
||||
| pathname_flow.rb:134:8:134:8 | l : | semmle.label | l : |
|
||||
| pathname_flow.rb:134:8:134:17 | call to realpath | semmle.label | call to realpath |
|
||||
subpaths
|
||||
#select
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @kind path-problem
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import TestUtilities.InlineFlowTest
|
||||
import PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, DefaultValueFlowConf conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "$@", source, source.toString()
|
||||
@@ -0,0 +1,135 @@
|
||||
require 'pathname'
|
||||
|
||||
def m_new
|
||||
pn = Pathname.new(source 'a')
|
||||
sink pn # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_plus
|
||||
a = Pathname.new(source 'a')
|
||||
b = Pathname.new(source 'b')
|
||||
sink(a + b) # $ hasTaintFlow=a $ hasTaintFlow=b
|
||||
end
|
||||
|
||||
def m_dirname
|
||||
pn = Pathname.new(source 'a')
|
||||
sink pn.dirname # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_each_filename
|
||||
a = Pathname.new(source 'a')
|
||||
a.each_filename do |x|
|
||||
sink x # $ hasTaintFlow=a
|
||||
end
|
||||
end
|
||||
|
||||
def m_expand_path
|
||||
a = Pathname.new(source 'a')
|
||||
sink a.expand_path() # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_join
|
||||
a = Pathname.new(source 'a')
|
||||
b = Pathname.new('foo')
|
||||
c = Pathname.new(source 'c')
|
||||
sink a.join(b, c) # $ hasTaintFlow=a $ hasTaintFlow=c
|
||||
end
|
||||
|
||||
def m_parent
|
||||
a = Pathname.new(source 'a')
|
||||
sink a.parent() # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_realpath
|
||||
a = Pathname.new(source 'a')
|
||||
sink a.realpath() # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_relative_path_from
|
||||
a = Pathname.new(source 'a')
|
||||
sink a.relative_path_from('/foo/bar') # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_to_path
|
||||
a = Pathname.new(source 'a')
|
||||
sink a.to_path # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_to_s
|
||||
a = Pathname.new(source 'a')
|
||||
sink a.to_s # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_plus
|
||||
a = Pathname.new(source 'a')
|
||||
b = a + 'foo'
|
||||
sink b # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_slash
|
||||
a = Pathname.new(source 'a')
|
||||
b = a / 'foo'
|
||||
sink b # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_basename
|
||||
a = Pathname.new(source 'a')
|
||||
b = a.basename
|
||||
sink b # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_cleanpath
|
||||
a = Pathname.new(source 'a')
|
||||
b = a.cleanpath
|
||||
sink b # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_sub
|
||||
a = Pathname.new(source 'a')
|
||||
b = a.sub('foo', 'bar')
|
||||
sink b # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
def m_sub_ext
|
||||
a = Pathname.new(source 'a')
|
||||
b = a.sub_ext('.txt')
|
||||
sink b # $ hasTaintFlow=a
|
||||
end
|
||||
|
||||
# Test flow through intermediate pathnames
|
||||
def intermediate_pathnames
|
||||
a = Pathname.new(source 'a')
|
||||
|
||||
b = a + 'foo'
|
||||
sink b.realpath # $ hasTaintFlow=a
|
||||
|
||||
c = a / 'foo'
|
||||
sink c.realpath # $ hasTaintFlow=a
|
||||
|
||||
d = a.basename
|
||||
sink d.realpath # $ hasTaintFlow=a
|
||||
|
||||
e = a.cleanpath
|
||||
sink e.realpath # $ hasTaintFlow=a
|
||||
|
||||
f = a.expand_path
|
||||
sink f.realpath # $ hasTaintFlow=a
|
||||
|
||||
g = a.join('foo')
|
||||
sink g.realpath # $ hasTaintFlow=a
|
||||
|
||||
h = a.realpath
|
||||
sink h.realpath # $ hasTaintFlow=a
|
||||
|
||||
i = a.relative_path_from('/foo/bar')
|
||||
sink i.realpath # $ hasTaintFlow=a
|
||||
|
||||
j = a.sub('foo', 'bar')
|
||||
sink j.realpath # $ hasTaintFlow=a
|
||||
|
||||
k = a.sub_ext('.txt')
|
||||
sink k.realpath # $ hasTaintFlow=a
|
||||
|
||||
l = a.to_path
|
||||
sink l.realpath # $ hasTaintFlow=a
|
||||
end
|
||||
@@ -19,19 +19,19 @@ edges
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:37:36:37:42 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:37:36:37:42 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:51:24:51:30 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:54:22:54:28 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:55:17:55:23 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:57:27:57:33 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:61:32:61:38 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:63:23:63:29 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:102:16:102:22 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:108:14:108:20 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:111:16:111:22 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:111:16:111:22 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:112:21:112:27 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:112:21:112:27 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:115:26:115:32 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:115:26:115:32 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:56:22:56:28 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:57:17:57:23 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:59:27:59:33 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:63:32:63:38 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:65:23:65:29 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:104:16:104:22 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:110:14:110:20 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:113:16:113:22 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:113:16:113:22 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:114:21:114:27 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:114:21:114:27 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:117:26:117:32 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:117:26:117:32 | tainted |
|
||||
| summaries.rb:1:20:1:36 | call to source : | summaries.rb:1:11:1:36 | call to identity : |
|
||||
| summaries.rb:1:20:1:36 | call to source : | summaries.rb:1:11:1:36 | call to identity : |
|
||||
| summaries.rb:4:12:7:3 | call to apply_block : | summaries.rb:9:6:9:13 | tainted2 |
|
||||
@@ -64,55 +64,58 @@ edges
|
||||
| summaries.rb:44:8:44:8 | t : | summaries.rb:44:8:44:27 | call to matchedByNameRcv |
|
||||
| summaries.rb:48:24:48:41 | call to source : | summaries.rb:48:8:48:42 | call to preserveTaint |
|
||||
| summaries.rb:51:24:51:30 | tainted : | summaries.rb:51:6:51:31 | call to namedArg |
|
||||
| summaries.rb:54:22:54:28 | tainted : | summaries.rb:54:6:54:29 | call to anyArg |
|
||||
| summaries.rb:55:17:55:23 | tainted : | summaries.rb:55:6:55:24 | call to anyArg |
|
||||
| summaries.rb:57:27:57:33 | tainted : | summaries.rb:57:6:57:34 | call to anyNamedArg |
|
||||
| summaries.rb:61:32:61:38 | tainted : | summaries.rb:61:6:61:39 | call to anyPositionFromOne |
|
||||
| summaries.rb:63:23:63:29 | tainted : | summaries.rb:63:40:63:40 | x : |
|
||||
| summaries.rb:63:40:63:40 | x : | summaries.rb:64:8:64:8 | x |
|
||||
| summaries.rb:71:24:71:53 | call to source : | summaries.rb:71:8:71:54 | call to preserveTaint |
|
||||
| summaries.rb:74:26:74:56 | call to source : | summaries.rb:74:8:74:57 | call to preserveTaint |
|
||||
| summaries.rb:77:15:77:29 | call to source : | summaries.rb:79:6:79:6 | a [element 1] : |
|
||||
| summaries.rb:77:15:77:29 | call to source : | summaries.rb:79:6:79:6 | a [element 1] : |
|
||||
| summaries.rb:77:15:77:29 | call to source : | summaries.rb:81:5:81:5 | a [element 1] : |
|
||||
| summaries.rb:77:15:77:29 | call to source : | summaries.rb:81:5:81:5 | a [element 1] : |
|
||||
| summaries.rb:77:32:77:46 | call to source : | summaries.rb:80:6:80:6 | a [element 2] : |
|
||||
| summaries.rb:77:32:77:46 | call to source : | summaries.rb:80:6:80:6 | a [element 2] : |
|
||||
| summaries.rb:77:32:77:46 | call to source : | summaries.rb:85:1:85:1 | a [element 2] : |
|
||||
| summaries.rb:77:32:77:46 | call to source : | summaries.rb:85:1:85:1 | a [element 2] : |
|
||||
| summaries.rb:79:6:79:6 | a [element 1] : | summaries.rb:79:6:79:9 | ...[...] |
|
||||
| summaries.rb:79:6:79:6 | a [element 1] : | summaries.rb:79:6:79:9 | ...[...] |
|
||||
| summaries.rb:80:6:80:6 | a [element 2] : | summaries.rb:80:6:80:9 | ...[...] |
|
||||
| summaries.rb:80:6:80:6 | a [element 2] : | summaries.rb:80:6:80:9 | ...[...] |
|
||||
| summaries.rb:81:5:81:5 | a [element 1] : | summaries.rb:81:5:81:22 | call to withElementOne [element 1] : |
|
||||
| summaries.rb:81:5:81:5 | a [element 1] : | summaries.rb:81:5:81:22 | call to withElementOne [element 1] : |
|
||||
| summaries.rb:81:5:81:22 | call to withElementOne [element 1] : | summaries.rb:83:6:83:6 | b [element 1] : |
|
||||
| summaries.rb:81:5:81:22 | call to withElementOne [element 1] : | summaries.rb:83:6:83:6 | b [element 1] : |
|
||||
| summaries.rb:83:6:83:6 | b [element 1] : | summaries.rb:83:6:83:9 | ...[...] |
|
||||
| summaries.rb:83:6:83:6 | b [element 1] : | summaries.rb:83:6:83:9 | ...[...] |
|
||||
| summaries.rb:85:1:85:1 | [post] a [element 2] : | summaries.rb:88:6:88:6 | a [element 2] : |
|
||||
| summaries.rb:85:1:85:1 | [post] a [element 2] : | summaries.rb:88:6:88:6 | a [element 2] : |
|
||||
| summaries.rb:85:1:85:1 | a [element 2] : | summaries.rb:85:1:85:1 | [post] a [element 2] : |
|
||||
| summaries.rb:85:1:85:1 | a [element 2] : | summaries.rb:85:1:85:1 | [post] a [element 2] : |
|
||||
| summaries.rb:88:6:88:6 | a [element 2] : | summaries.rb:88:6:88:9 | ...[...] |
|
||||
| summaries.rb:88:6:88:6 | a [element 2] : | summaries.rb:88:6:88:9 | ...[...] |
|
||||
| summaries.rb:91:1:91:1 | [post] x [@value] : | summaries.rb:92:6:92:6 | x [@value] : |
|
||||
| summaries.rb:91:1:91:1 | [post] x [@value] : | summaries.rb:92:6:92:6 | x [@value] : |
|
||||
| summaries.rb:91:13:91:26 | call to source : | summaries.rb:91:1:91:1 | [post] x [@value] : |
|
||||
| summaries.rb:91:13:91:26 | call to source : | summaries.rb:91:1:91:1 | [post] x [@value] : |
|
||||
| summaries.rb:92:6:92:6 | x [@value] : | summaries.rb:92:6:92:16 | call to get_value |
|
||||
| summaries.rb:92:6:92:6 | x [@value] : | summaries.rb:92:6:92:16 | call to get_value |
|
||||
| summaries.rb:102:16:102:22 | [post] tainted : | summaries.rb:108:14:108:20 | tainted : |
|
||||
| summaries.rb:102:16:102:22 | [post] tainted : | summaries.rb:111:16:111:22 | tainted |
|
||||
| summaries.rb:102:16:102:22 | [post] tainted : | summaries.rb:112:21:112:27 | tainted |
|
||||
| summaries.rb:102:16:102:22 | [post] tainted : | summaries.rb:115:26:115:32 | tainted |
|
||||
| summaries.rb:102:16:102:22 | tainted : | summaries.rb:102:16:102:22 | [post] tainted : |
|
||||
| summaries.rb:102:16:102:22 | tainted : | summaries.rb:102:25:102:25 | [post] y : |
|
||||
| summaries.rb:102:16:102:22 | tainted : | summaries.rb:102:33:102:33 | [post] z : |
|
||||
| summaries.rb:102:25:102:25 | [post] y : | summaries.rb:104:6:104:6 | y |
|
||||
| summaries.rb:102:33:102:33 | [post] z : | summaries.rb:105:6:105:6 | z |
|
||||
| summaries.rb:108:1:108:1 | [post] x : | summaries.rb:109:6:109:6 | x |
|
||||
| summaries.rb:108:14:108:20 | tainted : | summaries.rb:108:1:108:1 | [post] x : |
|
||||
| summaries.rb:53:15:53:31 | call to source : | summaries.rb:54:21:54:24 | args [element :foo] : |
|
||||
| summaries.rb:54:19:54:24 | ** ... [element :foo] : | summaries.rb:54:6:54:25 | call to namedArg |
|
||||
| summaries.rb:54:21:54:24 | args [element :foo] : | summaries.rb:54:19:54:24 | ** ... [element :foo] : |
|
||||
| summaries.rb:56:22:56:28 | tainted : | summaries.rb:56:6:56:29 | call to anyArg |
|
||||
| summaries.rb:57:17:57:23 | tainted : | summaries.rb:57:6:57:24 | call to anyArg |
|
||||
| summaries.rb:59:27:59:33 | tainted : | summaries.rb:59:6:59:34 | call to anyNamedArg |
|
||||
| summaries.rb:63:32:63:38 | tainted : | summaries.rb:63:6:63:39 | call to anyPositionFromOne |
|
||||
| summaries.rb:65:23:65:29 | tainted : | summaries.rb:65:40:65:40 | x : |
|
||||
| summaries.rb:65:40:65:40 | x : | summaries.rb:66:8:66:8 | x |
|
||||
| summaries.rb:73:24:73:53 | call to source : | summaries.rb:73:8:73:54 | call to preserveTaint |
|
||||
| summaries.rb:76:26:76:56 | call to source : | summaries.rb:76:8:76:57 | call to preserveTaint |
|
||||
| summaries.rb:79:15:79:29 | call to source : | summaries.rb:81:6:81:6 | a [element 1] : |
|
||||
| summaries.rb:79:15:79:29 | call to source : | summaries.rb:81:6:81:6 | a [element 1] : |
|
||||
| summaries.rb:79:15:79:29 | call to source : | summaries.rb:83:5:83:5 | a [element 1] : |
|
||||
| summaries.rb:79:15:79:29 | call to source : | summaries.rb:83:5:83:5 | a [element 1] : |
|
||||
| summaries.rb:79:32:79:46 | call to source : | summaries.rb:82:6:82:6 | a [element 2] : |
|
||||
| summaries.rb:79:32:79:46 | call to source : | summaries.rb:82:6:82:6 | a [element 2] : |
|
||||
| summaries.rb:79:32:79:46 | call to source : | summaries.rb:87:1:87:1 | a [element 2] : |
|
||||
| summaries.rb:79:32:79:46 | call to source : | summaries.rb:87:1:87:1 | a [element 2] : |
|
||||
| summaries.rb:81:6:81:6 | a [element 1] : | summaries.rb:81:6:81:9 | ...[...] |
|
||||
| summaries.rb:81:6:81:6 | a [element 1] : | summaries.rb:81:6:81:9 | ...[...] |
|
||||
| summaries.rb:82:6:82:6 | a [element 2] : | summaries.rb:82:6:82:9 | ...[...] |
|
||||
| summaries.rb:82:6:82:6 | a [element 2] : | summaries.rb:82:6:82:9 | ...[...] |
|
||||
| summaries.rb:83:5:83:5 | a [element 1] : | summaries.rb:83:5:83:22 | call to withElementOne [element 1] : |
|
||||
| summaries.rb:83:5:83:5 | a [element 1] : | summaries.rb:83:5:83:22 | call to withElementOne [element 1] : |
|
||||
| summaries.rb:83:5:83:22 | call to withElementOne [element 1] : | summaries.rb:85:6:85:6 | b [element 1] : |
|
||||
| summaries.rb:83:5:83:22 | call to withElementOne [element 1] : | summaries.rb:85:6:85:6 | b [element 1] : |
|
||||
| summaries.rb:85:6:85:6 | b [element 1] : | summaries.rb:85:6:85:9 | ...[...] |
|
||||
| summaries.rb:85:6:85:6 | b [element 1] : | summaries.rb:85:6:85:9 | ...[...] |
|
||||
| summaries.rb:87:1:87:1 | [post] a [element 2] : | summaries.rb:90:6:90:6 | a [element 2] : |
|
||||
| summaries.rb:87:1:87:1 | [post] a [element 2] : | summaries.rb:90:6:90:6 | a [element 2] : |
|
||||
| summaries.rb:87:1:87:1 | a [element 2] : | summaries.rb:87:1:87:1 | [post] a [element 2] : |
|
||||
| summaries.rb:87:1:87:1 | a [element 2] : | summaries.rb:87:1:87:1 | [post] a [element 2] : |
|
||||
| summaries.rb:90:6:90:6 | a [element 2] : | summaries.rb:90:6:90:9 | ...[...] |
|
||||
| summaries.rb:90:6:90:6 | a [element 2] : | summaries.rb:90:6:90:9 | ...[...] |
|
||||
| summaries.rb:93:1:93:1 | [post] x [@value] : | summaries.rb:94:6:94:6 | x [@value] : |
|
||||
| summaries.rb:93:1:93:1 | [post] x [@value] : | summaries.rb:94:6:94:6 | x [@value] : |
|
||||
| summaries.rb:93:13:93:26 | call to source : | summaries.rb:93:1:93:1 | [post] x [@value] : |
|
||||
| summaries.rb:93:13:93:26 | call to source : | summaries.rb:93:1:93:1 | [post] x [@value] : |
|
||||
| summaries.rb:94:6:94:6 | x [@value] : | summaries.rb:94:6:94:16 | call to get_value |
|
||||
| summaries.rb:94:6:94:6 | x [@value] : | summaries.rb:94:6:94:16 | call to get_value |
|
||||
| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:110:14:110:20 | tainted : |
|
||||
| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:113:16:113:22 | tainted |
|
||||
| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:114:21:114:27 | tainted |
|
||||
| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:117:26:117:32 | tainted |
|
||||
| summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:16:104:22 | [post] tainted : |
|
||||
| summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:25:104:25 | [post] y : |
|
||||
| summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:33:104:33 | [post] z : |
|
||||
| summaries.rb:104:25:104:25 | [post] y : | summaries.rb:106:6:106:6 | y |
|
||||
| summaries.rb:104:33:104:33 | [post] z : | summaries.rb:107:6:107:6 | z |
|
||||
| summaries.rb:110:1:110:1 | [post] x : | summaries.rb:111:6:111:6 | x |
|
||||
| summaries.rb:110:14:110:20 | tainted : | summaries.rb:110:1:110:1 | [post] x : |
|
||||
nodes
|
||||
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
|
||||
@@ -169,72 +172,76 @@ nodes
|
||||
| summaries.rb:48:24:48:41 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:51:6:51:31 | call to namedArg | semmle.label | call to namedArg |
|
||||
| summaries.rb:51:24:51:30 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:54:6:54:29 | call to anyArg | semmle.label | call to anyArg |
|
||||
| summaries.rb:54:22:54:28 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:55:6:55:24 | call to anyArg | semmle.label | call to anyArg |
|
||||
| summaries.rb:55:17:55:23 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:57:6:57:34 | call to anyNamedArg | semmle.label | call to anyNamedArg |
|
||||
| summaries.rb:57:27:57:33 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:61:6:61:39 | call to anyPositionFromOne | semmle.label | call to anyPositionFromOne |
|
||||
| summaries.rb:61:32:61:38 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:63:23:63:29 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:63:40:63:40 | x : | semmle.label | x : |
|
||||
| summaries.rb:64:8:64:8 | x | semmle.label | x |
|
||||
| summaries.rb:71:8:71:54 | call to preserveTaint | semmle.label | call to preserveTaint |
|
||||
| summaries.rb:71:24:71:53 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:74:8:74:57 | call to preserveTaint | semmle.label | call to preserveTaint |
|
||||
| summaries.rb:74:26:74:56 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:77:15:77:29 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:77:15:77:29 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:77:32:77:46 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:77:32:77:46 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:79:6:79:6 | a [element 1] : | semmle.label | a [element 1] : |
|
||||
| summaries.rb:79:6:79:6 | a [element 1] : | semmle.label | a [element 1] : |
|
||||
| summaries.rb:79:6:79:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:79:6:79:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:80:6:80:6 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:80:6:80:6 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:80:6:80:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:80:6:80:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:81:5:81:5 | a [element 1] : | semmle.label | a [element 1] : |
|
||||
| summaries.rb:81:5:81:5 | a [element 1] : | semmle.label | a [element 1] : |
|
||||
| summaries.rb:81:5:81:22 | call to withElementOne [element 1] : | semmle.label | call to withElementOne [element 1] : |
|
||||
| summaries.rb:81:5:81:22 | call to withElementOne [element 1] : | semmle.label | call to withElementOne [element 1] : |
|
||||
| summaries.rb:83:6:83:6 | b [element 1] : | semmle.label | b [element 1] : |
|
||||
| summaries.rb:83:6:83:6 | b [element 1] : | semmle.label | b [element 1] : |
|
||||
| summaries.rb:83:6:83:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:83:6:83:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:85:1:85:1 | [post] a [element 2] : | semmle.label | [post] a [element 2] : |
|
||||
| summaries.rb:85:1:85:1 | [post] a [element 2] : | semmle.label | [post] a [element 2] : |
|
||||
| summaries.rb:85:1:85:1 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:85:1:85:1 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:88:6:88:6 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:88:6:88:6 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:88:6:88:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:88:6:88:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:91:1:91:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
|
||||
| summaries.rb:91:1:91:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
|
||||
| summaries.rb:91:13:91:26 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:91:13:91:26 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:92:6:92:6 | x [@value] : | semmle.label | x [@value] : |
|
||||
| summaries.rb:92:6:92:6 | x [@value] : | semmle.label | x [@value] : |
|
||||
| summaries.rb:92:6:92:16 | call to get_value | semmle.label | call to get_value |
|
||||
| summaries.rb:92:6:92:16 | call to get_value | semmle.label | call to get_value |
|
||||
| summaries.rb:102:16:102:22 | [post] tainted : | semmle.label | [post] tainted : |
|
||||
| summaries.rb:102:16:102:22 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:102:25:102:25 | [post] y : | semmle.label | [post] y : |
|
||||
| summaries.rb:102:33:102:33 | [post] z : | semmle.label | [post] z : |
|
||||
| summaries.rb:104:6:104:6 | y | semmle.label | y |
|
||||
| summaries.rb:105:6:105:6 | z | semmle.label | z |
|
||||
| summaries.rb:108:1:108:1 | [post] x : | semmle.label | [post] x : |
|
||||
| summaries.rb:108:14:108:20 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:109:6:109:6 | x | semmle.label | x |
|
||||
| summaries.rb:111:16:111:22 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:111:16:111:22 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:112:21:112:27 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:112:21:112:27 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:115:26:115:32 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:115:26:115:32 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:53:15:53:31 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:54:6:54:25 | call to namedArg | semmle.label | call to namedArg |
|
||||
| summaries.rb:54:19:54:24 | ** ... [element :foo] : | semmle.label | ** ... [element :foo] : |
|
||||
| summaries.rb:54:21:54:24 | args [element :foo] : | semmle.label | args [element :foo] : |
|
||||
| summaries.rb:56:6:56:29 | call to anyArg | semmle.label | call to anyArg |
|
||||
| summaries.rb:56:22:56:28 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:57:6:57:24 | call to anyArg | semmle.label | call to anyArg |
|
||||
| summaries.rb:57:17:57:23 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:59:6:59:34 | call to anyNamedArg | semmle.label | call to anyNamedArg |
|
||||
| summaries.rb:59:27:59:33 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:63:6:63:39 | call to anyPositionFromOne | semmle.label | call to anyPositionFromOne |
|
||||
| summaries.rb:63:32:63:38 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:65:23:65:29 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:65:40:65:40 | x : | semmle.label | x : |
|
||||
| summaries.rb:66:8:66:8 | x | semmle.label | x |
|
||||
| summaries.rb:73:8:73:54 | call to preserveTaint | semmle.label | call to preserveTaint |
|
||||
| summaries.rb:73:24:73:53 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:76:8:76:57 | call to preserveTaint | semmle.label | call to preserveTaint |
|
||||
| summaries.rb:76:26:76:56 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:79:15:79:29 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:79:15:79:29 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:79:32:79:46 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:79:32:79:46 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:81:6:81:6 | a [element 1] : | semmle.label | a [element 1] : |
|
||||
| summaries.rb:81:6:81:6 | a [element 1] : | semmle.label | a [element 1] : |
|
||||
| summaries.rb:81:6:81:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:81:6:81:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:82:6:82:6 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:82:6:82:6 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:82:6:82:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:82:6:82:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:83:5:83:5 | a [element 1] : | semmle.label | a [element 1] : |
|
||||
| summaries.rb:83:5:83:5 | a [element 1] : | semmle.label | a [element 1] : |
|
||||
| summaries.rb:83:5:83:22 | call to withElementOne [element 1] : | semmle.label | call to withElementOne [element 1] : |
|
||||
| summaries.rb:83:5:83:22 | call to withElementOne [element 1] : | semmle.label | call to withElementOne [element 1] : |
|
||||
| summaries.rb:85:6:85:6 | b [element 1] : | semmle.label | b [element 1] : |
|
||||
| summaries.rb:85:6:85:6 | b [element 1] : | semmle.label | b [element 1] : |
|
||||
| summaries.rb:85:6:85:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:85:6:85:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:87:1:87:1 | [post] a [element 2] : | semmle.label | [post] a [element 2] : |
|
||||
| summaries.rb:87:1:87:1 | [post] a [element 2] : | semmle.label | [post] a [element 2] : |
|
||||
| summaries.rb:87:1:87:1 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:87:1:87:1 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:90:6:90:6 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:90:6:90:6 | a [element 2] : | semmle.label | a [element 2] : |
|
||||
| summaries.rb:90:6:90:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:90:6:90:9 | ...[...] | semmle.label | ...[...] |
|
||||
| summaries.rb:93:1:93:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
|
||||
| summaries.rb:93:1:93:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
|
||||
| summaries.rb:93:13:93:26 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:93:13:93:26 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:94:6:94:6 | x [@value] : | semmle.label | x [@value] : |
|
||||
| summaries.rb:94:6:94:6 | x [@value] : | semmle.label | x [@value] : |
|
||||
| summaries.rb:94:6:94:16 | call to get_value | semmle.label | call to get_value |
|
||||
| summaries.rb:94:6:94:16 | call to get_value | semmle.label | call to get_value |
|
||||
| summaries.rb:104:16:104:22 | [post] tainted : | semmle.label | [post] tainted : |
|
||||
| summaries.rb:104:16:104:22 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:104:25:104:25 | [post] y : | semmle.label | [post] y : |
|
||||
| summaries.rb:104:33:104:33 | [post] z : | semmle.label | [post] z : |
|
||||
| summaries.rb:106:6:106:6 | y | semmle.label | y |
|
||||
| summaries.rb:107:6:107:6 | z | semmle.label | z |
|
||||
| summaries.rb:110:1:110:1 | [post] x : | semmle.label | [post] x : |
|
||||
| summaries.rb:110:14:110:20 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:111:6:111:6 | x | semmle.label | x |
|
||||
| summaries.rb:113:16:113:22 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:113:16:113:22 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:114:21:114:27 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:114:21:114:27 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:117:26:117:32 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:117:26:117:32 | tainted | semmle.label | tainted |
|
||||
subpaths
|
||||
invalidSpecComponent
|
||||
#select
|
||||
@@ -265,32 +272,33 @@ invalidSpecComponent
|
||||
| summaries.rb:44:8:44:27 | call to matchedByNameRcv | summaries.rb:40:7:40:17 | call to source : | summaries.rb:44:8:44:27 | call to matchedByNameRcv | $@ | summaries.rb:40:7:40:17 | call to source : | call to source : |
|
||||
| summaries.rb:48:8:48:42 | call to preserveTaint | summaries.rb:48:24:48:41 | call to source : | summaries.rb:48:8:48:42 | call to preserveTaint | $@ | summaries.rb:48:24:48:41 | call to source : | call to source : |
|
||||
| summaries.rb:51:6:51:31 | call to namedArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:51:6:51:31 | call to namedArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:54:6:54:29 | call to anyArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:54:6:54:29 | call to anyArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:55:6:55:24 | call to anyArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:55:6:55:24 | call to anyArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:57:6:57:34 | call to anyNamedArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:57:6:57:34 | call to anyNamedArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:61:6:61:39 | call to anyPositionFromOne | summaries.rb:1:20:1:36 | call to source : | summaries.rb:61:6:61:39 | call to anyPositionFromOne | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:64:8:64:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:64:8:64:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:71:8:71:54 | call to preserveTaint | summaries.rb:71:24:71:53 | call to source : | summaries.rb:71:8:71:54 | call to preserveTaint | $@ | summaries.rb:71:24:71:53 | call to source : | call to source : |
|
||||
| summaries.rb:74:8:74:57 | call to preserveTaint | summaries.rb:74:26:74:56 | call to source : | summaries.rb:74:8:74:57 | call to preserveTaint | $@ | summaries.rb:74:26:74:56 | call to source : | call to source : |
|
||||
| summaries.rb:79:6:79:9 | ...[...] | summaries.rb:77:15:77:29 | call to source : | summaries.rb:79:6:79:9 | ...[...] | $@ | summaries.rb:77:15:77:29 | call to source : | call to source : |
|
||||
| summaries.rb:79:6:79:9 | ...[...] | summaries.rb:77:15:77:29 | call to source : | summaries.rb:79:6:79:9 | ...[...] | $@ | summaries.rb:77:15:77:29 | call to source : | call to source : |
|
||||
| summaries.rb:80:6:80:9 | ...[...] | summaries.rb:77:32:77:46 | call to source : | summaries.rb:80:6:80:9 | ...[...] | $@ | summaries.rb:77:32:77:46 | call to source : | call to source : |
|
||||
| summaries.rb:80:6:80:9 | ...[...] | summaries.rb:77:32:77:46 | call to source : | summaries.rb:80:6:80:9 | ...[...] | $@ | summaries.rb:77:32:77:46 | call to source : | call to source : |
|
||||
| summaries.rb:83:6:83:9 | ...[...] | summaries.rb:77:15:77:29 | call to source : | summaries.rb:83:6:83:9 | ...[...] | $@ | summaries.rb:77:15:77:29 | call to source : | call to source : |
|
||||
| summaries.rb:83:6:83:9 | ...[...] | summaries.rb:77:15:77:29 | call to source : | summaries.rb:83:6:83:9 | ...[...] | $@ | summaries.rb:77:15:77:29 | call to source : | call to source : |
|
||||
| summaries.rb:88:6:88:9 | ...[...] | summaries.rb:77:32:77:46 | call to source : | summaries.rb:88:6:88:9 | ...[...] | $@ | summaries.rb:77:32:77:46 | call to source : | call to source : |
|
||||
| summaries.rb:88:6:88:9 | ...[...] | summaries.rb:77:32:77:46 | call to source : | summaries.rb:88:6:88:9 | ...[...] | $@ | summaries.rb:77:32:77:46 | call to source : | call to source : |
|
||||
| summaries.rb:92:6:92:16 | call to get_value | summaries.rb:91:13:91:26 | call to source : | summaries.rb:92:6:92:16 | call to get_value | $@ | summaries.rb:91:13:91:26 | call to source : | call to source : |
|
||||
| summaries.rb:92:6:92:16 | call to get_value | summaries.rb:91:13:91:26 | call to source : | summaries.rb:92:6:92:16 | call to get_value | $@ | summaries.rb:91:13:91:26 | call to source : | call to source : |
|
||||
| summaries.rb:104:6:104:6 | y | summaries.rb:1:20:1:36 | call to source : | summaries.rb:104:6:104:6 | y | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:105:6:105:6 | z | summaries.rb:1:20:1:36 | call to source : | summaries.rb:105:6:105:6 | z | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:109:6:109:6 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:109:6:109:6 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:111:16:111:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:111:16:111:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:111:16:111:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:111:16:111:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:112:21:112:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:112:21:112:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:112:21:112:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:112:21:112:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:115:26:115:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:115:26:115:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:115:26:115:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:115:26:115:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:54:6:54:25 | call to namedArg | summaries.rb:53:15:53:31 | call to source : | summaries.rb:54:6:54:25 | call to namedArg | $@ | summaries.rb:53:15:53:31 | call to source : | call to source : |
|
||||
| summaries.rb:56:6:56:29 | call to anyArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:56:6:56:29 | call to anyArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:57:6:57:24 | call to anyArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:57:6:57:24 | call to anyArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:59:6:59:34 | call to anyNamedArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:59:6:59:34 | call to anyNamedArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:63:6:63:39 | call to anyPositionFromOne | summaries.rb:1:20:1:36 | call to source : | summaries.rb:63:6:63:39 | call to anyPositionFromOne | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:66:8:66:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:66:8:66:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:73:8:73:54 | call to preserveTaint | summaries.rb:73:24:73:53 | call to source : | summaries.rb:73:8:73:54 | call to preserveTaint | $@ | summaries.rb:73:24:73:53 | call to source : | call to source : |
|
||||
| summaries.rb:76:8:76:57 | call to preserveTaint | summaries.rb:76:26:76:56 | call to source : | summaries.rb:76:8:76:57 | call to preserveTaint | $@ | summaries.rb:76:26:76:56 | call to source : | call to source : |
|
||||
| summaries.rb:81:6:81:9 | ...[...] | summaries.rb:79:15:79:29 | call to source : | summaries.rb:81:6:81:9 | ...[...] | $@ | summaries.rb:79:15:79:29 | call to source : | call to source : |
|
||||
| summaries.rb:81:6:81:9 | ...[...] | summaries.rb:79:15:79:29 | call to source : | summaries.rb:81:6:81:9 | ...[...] | $@ | summaries.rb:79:15:79:29 | call to source : | call to source : |
|
||||
| summaries.rb:82:6:82:9 | ...[...] | summaries.rb:79:32:79:46 | call to source : | summaries.rb:82:6:82:9 | ...[...] | $@ | summaries.rb:79:32:79:46 | call to source : | call to source : |
|
||||
| summaries.rb:82:6:82:9 | ...[...] | summaries.rb:79:32:79:46 | call to source : | summaries.rb:82:6:82:9 | ...[...] | $@ | summaries.rb:79:32:79:46 | call to source : | call to source : |
|
||||
| summaries.rb:85:6:85:9 | ...[...] | summaries.rb:79:15:79:29 | call to source : | summaries.rb:85:6:85:9 | ...[...] | $@ | summaries.rb:79:15:79:29 | call to source : | call to source : |
|
||||
| summaries.rb:85:6:85:9 | ...[...] | summaries.rb:79:15:79:29 | call to source : | summaries.rb:85:6:85:9 | ...[...] | $@ | summaries.rb:79:15:79:29 | call to source : | call to source : |
|
||||
| summaries.rb:90:6:90:9 | ...[...] | summaries.rb:79:32:79:46 | call to source : | summaries.rb:90:6:90:9 | ...[...] | $@ | summaries.rb:79:32:79:46 | call to source : | call to source : |
|
||||
| summaries.rb:90:6:90:9 | ...[...] | summaries.rb:79:32:79:46 | call to source : | summaries.rb:90:6:90:9 | ...[...] | $@ | summaries.rb:79:32:79:46 | call to source : | call to source : |
|
||||
| summaries.rb:94:6:94:16 | call to get_value | summaries.rb:93:13:93:26 | call to source : | summaries.rb:94:6:94:16 | call to get_value | $@ | summaries.rb:93:13:93:26 | call to source : | call to source : |
|
||||
| summaries.rb:94:6:94:16 | call to get_value | summaries.rb:93:13:93:26 | call to source : | summaries.rb:94:6:94:16 | call to get_value | $@ | summaries.rb:93:13:93:26 | call to source : | call to source : |
|
||||
| summaries.rb:106:6:106:6 | y | summaries.rb:1:20:1:36 | call to source : | summaries.rb:106:6:106:6 | y | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:107:6:107:6 | z | summaries.rb:1:20:1:36 | call to source : | summaries.rb:107:6:107:6 | z | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:111:6:111:6 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:111:6:111:6 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:113:16:113:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:113:16:113:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:113:16:113:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:113:16:113:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:114:21:114:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:114:21:114:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:114:21:114:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:114:21:114:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:117:26:117:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:117:26:117:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:117:26:117:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:117:26:117:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
warning
|
||||
| CSV type row should have 5 columns but has 2: test;TooFewColumns |
|
||||
| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns |
|
||||
|
||||
@@ -132,7 +132,7 @@ class CustomValueSink extends DefaultValueFlowConf {
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
super.isSink(sink)
|
||||
or
|
||||
sink = ModelOutput::getASinkNode("test-sink").getARhs()
|
||||
sink = ModelOutput::getASinkNode("test-sink").asSink()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ class CustomTaintSink extends DefaultTaintFlowConf {
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
super.isSink(sink)
|
||||
or
|
||||
sink = ModelOutput::getASinkNode("test-sink").getARhs()
|
||||
sink = ModelOutput::getASinkNode("test-sink").asSink()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ end
|
||||
|
||||
sink(Foo.namedArg(foo: tainted)) # $ hasTaintFlow=tainted
|
||||
sink(Foo.namedArg(tainted))
|
||||
args = { foo: source("tainted") }
|
||||
sink(Foo.namedArg(**args)) # $ hasTaintFlow=tainted
|
||||
|
||||
sink(Foo.anyArg(foo: tainted)) # $ hasTaintFlow=tainted
|
||||
sink(Foo.anyArg(tainted)) # $ hasTaintFlow=tainted
|
||||
|
||||
@@ -1,60 +1,93 @@
|
||||
actionControllerControllerClasses
|
||||
| ActiveRecord.rb:23:1:39:3 | FooController |
|
||||
| ActiveRecord.rb:41:1:64:3 | BarController |
|
||||
| ActiveRecord.rb:66:1:70:3 | BazController |
|
||||
| active_record/ActiveRecord.rb:23:1:39:3 | FooController |
|
||||
| active_record/ActiveRecord.rb:41:1:64:3 | BarController |
|
||||
| active_record/ActiveRecord.rb:66:1:94:3 | BazController |
|
||||
| active_record/ActiveRecord.rb:96:1:104:3 | AnnotatedController |
|
||||
| app/controllers/comments_controller.rb:1:1:7:3 | CommentsController |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController |
|
||||
| app/controllers/photos_controller.rb:1:1:4:3 | PhotosController |
|
||||
| app/controllers/posts_controller.rb:1:1:10:3 | PostsController |
|
||||
| app/controllers/users/notifications_controller.rb:2:3:5:5 | NotificationsController |
|
||||
actionControllerActionMethods
|
||||
| ActiveRecord.rb:27:3:38:5 | some_request_handler |
|
||||
| ActiveRecord.rb:42:3:47:5 | some_other_request_handler |
|
||||
| ActiveRecord.rb:49:3:63:5 | safe_paths |
|
||||
| ActiveRecord.rb:67:3:69:5 | yet_another_handler |
|
||||
| active_record/ActiveRecord.rb:27:3:38:5 | some_request_handler |
|
||||
| active_record/ActiveRecord.rb:42:3:47:5 | some_other_request_handler |
|
||||
| active_record/ActiveRecord.rb:49:3:63:5 | safe_paths |
|
||||
| active_record/ActiveRecord.rb:67:3:69:5 | yet_another_handler |
|
||||
| active_record/ActiveRecord.rb:71:3:73:5 | create1 |
|
||||
| active_record/ActiveRecord.rb:75:3:77:5 | create2 |
|
||||
| active_record/ActiveRecord.rb:79:3:81:5 | create3 |
|
||||
| active_record/ActiveRecord.rb:83:3:85:5 | update1 |
|
||||
| active_record/ActiveRecord.rb:87:3:89:5 | update2 |
|
||||
| active_record/ActiveRecord.rb:91:3:93:5 | update3 |
|
||||
| active_record/ActiveRecord.rb:97:3:99:5 | index |
|
||||
| active_record/ActiveRecord.rb:101:3:103:5 | unsafe_action |
|
||||
| app/controllers/comments_controller.rb:2:3:3:5 | index |
|
||||
| app/controllers/comments_controller.rb:5:3:6:5 | show |
|
||||
| app/controllers/foo/bars_controller.rb:5:3:7:5 | index |
|
||||
| app/controllers/foo/bars_controller.rb:9:3:18:5 | show_debug |
|
||||
| app/controllers/foo/bars_controller.rb:20:3:24:5 | show |
|
||||
| app/controllers/foo/bars_controller.rb:26:3:28:5 | go_back |
|
||||
| app/controllers/foo/bars_controller.rb:30:3:32:5 | go_back_2 |
|
||||
| app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/controllers/posts_controller.rb:2:3:3:5 | index |
|
||||
| app/controllers/posts_controller.rb:5:3:6:5 | show |
|
||||
| app/controllers/posts_controller.rb:8:3:9:5 | upvote |
|
||||
| app/controllers/users/notifications_controller.rb:3:5:4:7 | mark_as_read |
|
||||
paramsCalls
|
||||
| ActiveRecord.rb:28:30:28:35 | call to params |
|
||||
| ActiveRecord.rb:29:29:29:34 | call to params |
|
||||
| ActiveRecord.rb:30:31:30:36 | call to params |
|
||||
| ActiveRecord.rb:32:21:32:26 | call to params |
|
||||
| ActiveRecord.rb:34:34:34:39 | call to params |
|
||||
| ActiveRecord.rb:35:23:35:28 | call to params |
|
||||
| ActiveRecord.rb:35:38:35:43 | call to params |
|
||||
| ActiveRecord.rb:43:10:43:15 | call to params |
|
||||
| ActiveRecord.rb:50:11:50:16 | call to params |
|
||||
| ActiveRecord.rb:54:12:54:17 | call to params |
|
||||
| ActiveRecord.rb:59:12:59:17 | call to params |
|
||||
| ActiveRecord.rb:62:15:62:20 | call to params |
|
||||
| ActiveRecord.rb:68:21:68:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
|
||||
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
|
||||
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
|
||||
| active_record/ActiveRecord.rb:32:21:32:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:34:34:34:39 | call to params |
|
||||
| active_record/ActiveRecord.rb:35:23:35:28 | call to params |
|
||||
| active_record/ActiveRecord.rb:35:38:35:43 | call to params |
|
||||
| active_record/ActiveRecord.rb:43:10:43:15 | call to params |
|
||||
| active_record/ActiveRecord.rb:50:11:50:16 | call to params |
|
||||
| active_record/ActiveRecord.rb:54:12:54:17 | call to params |
|
||||
| active_record/ActiveRecord.rb:59:12:59:17 | call to params |
|
||||
| active_record/ActiveRecord.rb:62:15:62:20 | call to params |
|
||||
| active_record/ActiveRecord.rb:68:21:68:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:72:18:72:23 | call to params |
|
||||
| active_record/ActiveRecord.rb:76:24:76:29 | call to params |
|
||||
| active_record/ActiveRecord.rb:76:49:76:54 | call to params |
|
||||
| active_record/ActiveRecord.rb:80:25:80:30 | call to params |
|
||||
| active_record/ActiveRecord.rb:80:50:80:55 | call to params |
|
||||
| active_record/ActiveRecord.rb:84:21:84:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:88:27:88:32 | call to params |
|
||||
| active_record/ActiveRecord.rb:88:52:88:57 | call to params |
|
||||
| active_record/ActiveRecord.rb:92:28:92:33 | call to params |
|
||||
| active_record/ActiveRecord.rb:92:53:92:58 | call to params |
|
||||
| active_record/ActiveRecord.rb:102:59:102:64 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params |
|
||||
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
|
||||
paramsSources
|
||||
| ActiveRecord.rb:28:30:28:35 | call to params |
|
||||
| ActiveRecord.rb:29:29:29:34 | call to params |
|
||||
| ActiveRecord.rb:30:31:30:36 | call to params |
|
||||
| ActiveRecord.rb:32:21:32:26 | call to params |
|
||||
| ActiveRecord.rb:34:34:34:39 | call to params |
|
||||
| ActiveRecord.rb:35:23:35:28 | call to params |
|
||||
| ActiveRecord.rb:35:38:35:43 | call to params |
|
||||
| ActiveRecord.rb:43:10:43:15 | call to params |
|
||||
| ActiveRecord.rb:50:11:50:16 | call to params |
|
||||
| ActiveRecord.rb:54:12:54:17 | call to params |
|
||||
| ActiveRecord.rb:59:12:59:17 | call to params |
|
||||
| ActiveRecord.rb:62:15:62:20 | call to params |
|
||||
| ActiveRecord.rb:68:21:68:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
|
||||
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
|
||||
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
|
||||
| active_record/ActiveRecord.rb:32:21:32:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:34:34:34:39 | call to params |
|
||||
| active_record/ActiveRecord.rb:35:23:35:28 | call to params |
|
||||
| active_record/ActiveRecord.rb:35:38:35:43 | call to params |
|
||||
| active_record/ActiveRecord.rb:43:10:43:15 | call to params |
|
||||
| active_record/ActiveRecord.rb:50:11:50:16 | call to params |
|
||||
| active_record/ActiveRecord.rb:54:12:54:17 | call to params |
|
||||
| active_record/ActiveRecord.rb:59:12:59:17 | call to params |
|
||||
| active_record/ActiveRecord.rb:62:15:62:20 | call to params |
|
||||
| active_record/ActiveRecord.rb:68:21:68:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:72:18:72:23 | call to params |
|
||||
| active_record/ActiveRecord.rb:76:24:76:29 | call to params |
|
||||
| active_record/ActiveRecord.rb:76:49:76:54 | call to params |
|
||||
| active_record/ActiveRecord.rb:80:25:80:30 | call to params |
|
||||
| active_record/ActiveRecord.rb:80:50:80:55 | call to params |
|
||||
| active_record/ActiveRecord.rb:84:21:84:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:88:27:88:32 | call to params |
|
||||
| active_record/ActiveRecord.rb:88:52:88:57 | call to params |
|
||||
| active_record/ActiveRecord.rb:92:28:92:33 | call to params |
|
||||
| active_record/ActiveRecord.rb:92:53:92:58 | call to params |
|
||||
| active_record/ActiveRecord.rb:102:59:102:64 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
|
||||
@@ -66,10 +99,12 @@ cookiesSources
|
||||
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
|
||||
redirectToCalls
|
||||
| app/controllers/foo/bars_controller.rb:17:5:17:30 | call to redirect_to |
|
||||
| app/controllers/foo/bars_controller.rb:27:5:27:39 | call to redirect_back_or_to |
|
||||
| app/controllers/foo/bars_controller.rb:31:5:31:56 | call to redirect_back |
|
||||
actionControllerHelperMethods
|
||||
getAssociatedControllerClasses
|
||||
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
|
||||
controllerTemplateFiles
|
||||
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
|
||||
|
||||
@@ -54,3 +54,15 @@ underscore
|
||||
| HTTPServerRequest | httpserver_request |
|
||||
| LotsOfCapitalLetters | lots_of_capital_letters |
|
||||
| invalid | invalid |
|
||||
mimeTypeInstances
|
||||
| action_dispatch/mime_type.rb:2:6:2:28 | Use getMember("Mime").getMethod("fetch").getReturn() |
|
||||
| action_dispatch/mime_type.rb:3:6:3:32 | Use getMember("Mime").getMember("Type").getMethod("new").getReturn() |
|
||||
| action_dispatch/mime_type.rb:4:6:4:35 | Use getMember("Mime").getMember("Type").getMethod("lookup").getReturn() |
|
||||
| action_dispatch/mime_type.rb:5:6:5:43 | Use getMember("Mime").getMember("Type").getMethod("lookup_by_extension").getReturn() |
|
||||
| action_dispatch/mime_type.rb:6:6:6:47 | Use getMember("Mime").getMember("Type").getMethod("register").getReturn() |
|
||||
| action_dispatch/mime_type.rb:7:6:7:64 | Use getMember("Mime").getMember("Type").getMethod("register_alias").getReturn() |
|
||||
mimeTypeMatchRegExpInterpretations
|
||||
| action_dispatch/mime_type.rb:11:11:11:19 | "foo/bar" |
|
||||
| action_dispatch/mime_type.rb:12:7:12:15 | "foo/bar" |
|
||||
| action_dispatch/mime_type.rb:13:11:13:11 | s |
|
||||
| action_dispatch/mime_type.rb:14:7:14:7 | s |
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.frameworks.ActionDispatch
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Regexp as RE
|
||||
|
||||
query predicate actionDispatchRoutes(
|
||||
ActionDispatch::Route r, string method, string path, string controller, string action
|
||||
ActionDispatch::Routing::Route r, string method, string path, string controller, string action
|
||||
) {
|
||||
r.getHttpMethod() = method and
|
||||
r.getPath() = path and
|
||||
@@ -12,15 +16,25 @@ query predicate actionDispatchRoutes(
|
||||
}
|
||||
|
||||
query predicate actionDispatchControllerMethods(
|
||||
ActionDispatch::Route r, ActionControllerActionMethod m
|
||||
ActionDispatch::Routing::Route r, ActionControllerActionMethod m
|
||||
) {
|
||||
m.getARoute() = r
|
||||
}
|
||||
|
||||
query predicate underscore(string input, string output) {
|
||||
output = ActionDispatch::underscore(input) and
|
||||
output = ActionDispatch::Routing::underscore(input) and
|
||||
input in [
|
||||
"Foo", "FooBar", "Foo::Bar", "FooBar::Baz", "Foo::Bar::Baz", "Foo::Bar::BazQuux", "invalid",
|
||||
"HTTPServerRequest", "LotsOfCapitalLetters"
|
||||
]
|
||||
}
|
||||
|
||||
query predicate mimeTypeInstances(API::Node n) {
|
||||
n = ModelOutput::getATypeNode("actiondispatch", "Mime::Type")
|
||||
}
|
||||
|
||||
query predicate mimeTypeMatchRegExpInterpretations(
|
||||
ActionDispatch::MimeTypeMatchRegExpInterpretation s
|
||||
) {
|
||||
any()
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ rawCalls
|
||||
renderCalls
|
||||
| app/controllers/foo/bars_controller.rb:6:5:6:37 | call to render |
|
||||
| app/controllers/foo/bars_controller.rb:23:5:23:76 | call to render |
|
||||
| app/controllers/foo/bars_controller.rb:29:5:29:17 | call to render |
|
||||
| app/controllers/foo/bars_controller.rb:37:5:37:17 | call to render |
|
||||
| app/views/foo/bars/show.html.erb:31:5:31:89 | call to render |
|
||||
renderToCalls
|
||||
| app/controllers/foo/bars_controller.rb:15:16:15:97 | call to render_to_string |
|
||||
|
||||
@@ -5,11 +5,13 @@ import codeql.ruby.DataFlow
|
||||
query predicate systemCalls(
|
||||
PosixSpawn::SystemCall call, DataFlow::Node arg, boolean shellInterpreted
|
||||
) {
|
||||
arg = call.getAnArgument() and
|
||||
if call.isShellInterpreted(arg) then shellInterpreted = true else shellInterpreted = false
|
||||
call.isShellInterpreted(arg) and shellInterpreted = true
|
||||
or
|
||||
not call.isShellInterpreted(arg) and arg = call.getAnArgument() and shellInterpreted = false
|
||||
}
|
||||
|
||||
query predicate childCalls(PosixSpawn::ChildCall call, DataFlow::Node arg, boolean shellInterpreted) {
|
||||
arg = call.getAnArgument() and
|
||||
if call.isShellInterpreted(arg) then shellInterpreted = true else shellInterpreted = false
|
||||
call.isShellInterpreted(arg) and shellInterpreted = true
|
||||
or
|
||||
not call.isShellInterpreted(arg) and arg = call.getAnArgument() and shellInterpreted = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
| action_cable.rb:1:1:1:54 | call to new |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user