use crate::analyze::const_eval::{self, MacroConstantMap, ValueRange, VarRangeMap};
use crate::analyze::null_state::NullState;
use std::collections::{HashMap, HashSet};
use tree_sitter::Node;
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct FunctionSummary {
pub frees_params: HashSet<usize>,
pub can_return_null: bool,
pub returns_allocation: bool,
pub checks_null_params: HashSet<usize>,
pub modifies_params: HashSet<usize>,
pub dereferences_params: HashSet<usize>,
pub never_returns: bool,
pub callsite_param_null_states: HashMap<usize, NullState>,
#[serde(default)]
pub callsite_param_field_null_states: HashMap<usize, HashMap<String, NullState>>,
#[serde(default)]
pub callsite_param_pointee_null_states: HashMap<usize, NullState>,
pub return_range: Option<ValueRange>,
#[serde(default)]
pub param_passthroughs: HashMap<usize, Vec<(String, usize)>>,
#[serde(default)]
pub frees_param_fields: HashMap<usize, HashSet<String>>,
#[serde(default)]
pub has_env03_taint_source: bool,
#[serde(default)]
pub returns_tainted: bool,
#[serde(default)]
pub returns_from_callees: HashSet<String>,
#[serde(default)]
pub has_relative_command_write: bool,
#[serde(default)]
pub callsite_param_const_int: HashMap<usize, i64>,
#[serde(default)]
pub callsite_param_buffer_size: HashMap<usize, usize>,
}
pub const ENV03_TAINT_SOURCE_FUNCTIONS: &[&str] = &[
"recv",
"recvfrom",
"recvmsg",
"WSARecv",
"WSARecvFrom",
"accept",
"read",
"fread",
"fgets",
"gets",
"getchar",
"getc",
"fgetc",
"scanf",
"fscanf",
"sscanf",
"vscanf",
"vfscanf",
"fgetws",
"getwchar",
"getwc",
"fgetwc",
"wscanf",
"fwscanf",
"swscanf",
"vwscanf",
"vfwscanf",
"_getws",
"_getws_s",
"getenv",
"secure_getenv",
"_wgetenv",
"_wgetenv_s",
"ReadFile",
"ReadConsole",
"ReadConsoleA",
"ReadConsoleW",
"RegQueryValueExA",
"RegQueryValueExW",
];
fn body_contains_taint_source(body_text: &str) -> bool {
ENV03_TAINT_SOURCE_FUNCTIONS
.iter()
.any(|name| body_text.contains(&format!("{}(", name)))
}
fn body_contains_alias(body_text: &str, aliases: &[String]) -> bool {
aliases
.iter()
.any(|alias| body_text.contains(&format!("{}(", alias)))
}
fn body_has_relative_command_write(
body: &Node,
source: &str,
string_macros: &HashMap<String, String>,
) -> bool {
let mut found = false;
walk_for_relative_command_write(body, source, string_macros, &mut found);
found
}
fn walk_for_relative_command_write(
node: &Node,
source: &str,
string_macros: &HashMap<String, String>,
found: &mut bool,
) {
if *found {
return;
}
if node.kind() == "call_expression" {
if let Some(func) = node.child_by_field_name("function") {
let raw = func.utf8_text(source.as_bytes()).unwrap_or("");
let ident = raw
.rsplit(|c: char| !c.is_alphanumeric() && c != '_')
.next()
.unwrap_or(raw);
if matches!(
ident,
"strcpy"
| "strcat"
| "stncpy"
| "strncat"
| "wcscpy"
| "wcscat"
| "wcsncpy"
| "wcsncat"
) {
if let Some(args) = node.child_by_field_name("arguments") {
let named: Vec<_> = (0..args.child_count())
.filter_map(|i| args.child(i))
.filter(|c| c.is_named())
.collect();
if let Some(second) = named.get(1) {
let s = *second;
if s.kind() == "identifier" {
let nm = s.utf8_text(source.as_bytes()).unwrap_or("");
if const_eval::is_relative_command_macro(string_macros, nm) {
*found = true;
return;
}
}
}
}
}
}
return;
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if is_real_nested_function_definition(&child, source) {
continue;
}
walk_for_relative_command_write(&child, source, string_macros, found);
if *found {
return;
}
}
}
}
pub fn compute_summaries(
root: &Node,
source: &str,
macros: &MacroConstantMap,
compute_return_ranges: bool,
taint_source_aliases: &[String],
string_macros: &HashMap<String, String>,
function_macros: &HashMap<String, crate::analyze::macro_expand::FunctionMacro>,
) -> HashMap<String, FunctionSummary> {
let mut summaries = HashMap::new();
collect_function_summaries(
root,
source,
macros,
compute_return_ranges,
taint_source_aliases,
string_macros,
function_macros,
&mut summaries,
);
summaries
}
#[allow(clippy::too_many_arguments)]
fn collect_function_summaries(
node: &Node,
source: &str,
macros: &MacroConstantMap,
compute_return_ranges: bool,
taint_source_aliases: &[String],
string_macros: &HashMap<String, String>,
function_macros: &HashMap<String, crate::analyze::macro_expand::FunctionMacro>,
summaries: &mut HashMap<String, FunctionSummary>,
) {
if node.kind() == "function_definition" && !is_macro_function_definition(node) {
if let Some(name) = extract_function_name(node, source) {
let summary = analyze_function(
node,
source,
macros,
compute_return_ranges,
taint_source_aliases,
string_macros,
function_macros,
);
summaries.insert(name, summary);
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
collect_function_summaries(
&child,
source,
macros,
compute_return_ranges,
taint_source_aliases,
string_macros,
function_macros,
summaries,
);
}
}
}
fn is_macro_function_definition(node: &Node) -> bool {
node.kind() == "function_definition"
&& node
.child_by_field_name("declarator")
.map(|d| d.kind() == "parenthesized_declarator")
.unwrap_or(false)
}
fn is_real_nested_function_definition(node: &Node, source: &str) -> bool {
if node.kind() != "function_definition" || is_macro_function_definition(node) {
return false;
}
match extract_function_name(node, source) {
Some(name) => !is_c_keyword(&name),
None => true,
}
}
fn is_c_keyword(name: &str) -> bool {
matches!(
name,
"if" | "else"
| "for"
| "while"
| "do"
| "switch"
| "case"
| "default"
| "return"
| "break"
| "continue"
| "goto"
| "sizeof"
| "typedef"
| "struct"
| "union"
| "enum"
)
}
fn find_nested_function_boundary(node: &Node, source: &str) -> Option<usize> {
for i in 0..node.child_count() {
let child = node.child(i)?;
if is_real_nested_function_definition(&child, source) {
return Some(child.start_byte());
}
if let Some(boundary) = find_nested_function_boundary(&child, source) {
return Some(boundary);
}
}
None
}
fn analyze_function(
func_node: &Node,
source: &str,
macros: &MacroConstantMap,
compute_return_ranges: bool,
taint_source_aliases: &[String],
string_macros: &HashMap<String, String>,
function_macros: &HashMap<String, crate::analyze::macro_expand::FunctionMacro>,
) -> FunctionSummary {
let mut summary = FunctionSummary::default();
let params = collect_param_names(func_node, source);
let is_pointer_return;
let is_void_return;
if let Some(return_type) = func_node.child_by_field_name("type") {
let type_text = return_type.utf8_text(source.as_bytes()).unwrap_or("");
let decl_text = func_node
.child_by_field_name("declarator")
.map(|d| d.utf8_text(source.as_bytes()).unwrap_or(""))
.unwrap_or("");
is_pointer_return = decl_text.contains('*');
is_void_return = type_text == "void";
if is_pointer_return {
summary.can_return_null = true;
}
if is_void_return {
summary.can_return_null = false;
}
} else {
is_pointer_return = false;
is_void_return = false;
}
if let Some(body) = func_node.child_by_field_name("body") {
let text_end = if body.has_error() {
find_nested_function_boundary(&body, source).unwrap_or_else(|| body.end_byte())
} else {
body.end_byte()
};
let body_text = &source[body.start_byte()..text_end];
summary.never_returns = check_never_returns(body_text);
summary.returns_allocation = body_text.contains("malloc(")
|| body_text.contains("calloc(")
|| body_text.contains("realloc(")
|| body_text.contains("aligned_alloc(");
summary.has_env03_taint_source = body_contains_taint_source(body_text)
|| body_contains_alias(body_text, taint_source_aliases);
if !string_macros.is_empty() {
summary.has_relative_command_write =
body_has_relative_command_write(&body, source, string_macros);
}
if !is_void_return {
summary.returns_tainted = summary.has_env03_taint_source;
}
collect_returns_from_callees(&body, source, &mut summary.returns_from_callees);
if !summary.can_return_null {
summary.can_return_null = check_returns_null(&body, source);
}
if is_pointer_return && summary.can_return_null {
if check_all_returns_nonnull(&body, source) {
summary.can_return_null = false;
}
}
analyze_param_usage(
&body,
source,
body_text,
¶ms,
function_macros,
&mut summary,
);
if compute_return_ranges && !is_void_return && !is_pointer_return {
summary.return_range = compute_return_range(&body, source, macros);
}
}
summary
}
pub fn collect_param_names(func_node: &Node, source: &str) -> Vec<String> {
let mut params = Vec::new();
if let Some(declarator) = func_node.child_by_field_name("declarator") {
collect_params_recursive(&declarator, source, &mut params);
}
params
}
fn collect_params_recursive(node: &Node, source: &str, params: &mut Vec<String>) {
if node.kind() == "function_declarator" {
if let Some(param_list) = node.child_by_field_name("parameters") {
for i in 0..param_list.child_count() {
if let Some(param) = param_list.child(i) {
if param.kind() == "parameter_declaration" {
if let Some(decl) = param.child_by_field_name("declarator") {
let name = extract_leaf_identifier(&decl, source);
params.push(name);
} else {
params.push(String::new()); }
}
}
}
}
} else {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
collect_params_recursive(&child, source, params);
}
}
}
}
fn check_never_returns(body_text: &str) -> bool {
if !body_text.contains("abort(")
&& !body_text.contains("exit(")
&& !body_text.contains("_Exit(")
&& !body_text.contains("longjmp(")
&& !body_text.contains("quick_exit(")
{
return false;
}
let has_return = body_text.contains("return ");
let ends_with_noreturn = body_text.contains("abort()")
|| body_text.contains("exit(EXIT_FAILURE)")
|| body_text.contains("exit(1)")
|| body_text.contains("exit(EXIT_SUCCESS)")
|| body_text.contains("exit(0)");
if !has_return && ends_with_noreturn {
return true;
}
false
}
fn check_all_returns_nonnull(body: &Node, source: &str) -> bool {
let mut found_any = false;
let result = check_returns_all_nonnull_recursive(body, source, &mut found_any);
found_any && result
}
fn check_returns_all_nonnull_recursive(node: &Node, source: &str, found_any: &mut bool) -> bool {
if node.kind() == "return_statement" {
*found_any = true;
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "return" || child.kind() == ";" {
continue;
}
if child.kind() == "pointer_expression" {
if let Some(op) = child.child_by_field_name("operator") {
if op.utf8_text(source.as_bytes()).unwrap_or("") == "&" {
return true;
}
}
}
let text = child.utf8_text(source.as_bytes()).unwrap_or("").trim();
if text.starts_with('&') {
return true;
}
return false;
}
}
return false;
}
if node.kind() == "function_definition" {
return true; }
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if !check_returns_all_nonnull_recursive(&child, source, found_any) {
return false;
}
}
}
true
}
fn check_returns_null(body: &Node, source: &str) -> bool {
if body.kind() == "return_statement" {
for i in 0..body.child_count() {
if let Some(child) = body.child(i) {
if child.kind() != "return" {
let text = child.utf8_text(source.as_bytes()).unwrap_or("").trim();
if text == "NULL" || text == "0" || text == "nullptr" {
return true;
}
}
}
}
}
for i in 0..body.child_count() {
if let Some(child) = body.child(i) {
if is_real_nested_function_definition(&child, source) {
continue;
}
if check_returns_null(&child, source) {
return true;
}
}
}
false
}
fn analyze_param_usage(
body: &Node,
source: &str,
body_text: &str,
params: &[String],
function_macros: &HashMap<String, crate::analyze::macro_expand::FunctionMacro>,
summary: &mut FunctionSummary,
) {
for (idx, param_name) in params.iter().enumerate() {
if param_name.is_empty() {
continue;
}
if body_text.contains(&format!("free({})", param_name))
|| body_text.contains(&format!("free( {} )", param_name))
{
summary.frees_params.insert(idx);
}
if body_matches_null_check(body_text, param_name)
|| body_matches_alias_null_check(body_text, param_name)
{
summary.checks_null_params.insert(idx);
}
if body_text.contains(&format!("*{} =", param_name))
|| body_text.contains(&format!("{}->", param_name))
|| body_text.contains(&format!("{}[", param_name))
{
summary.modifies_params.insert(idx);
}
if body_text.contains(&format!("*{}", param_name))
|| body_text.contains(&format!("{}->", param_name))
|| body_text.contains(&format!("{}[", param_name))
|| body_text.contains(&format!("*){}", param_name))
{
summary.dereferences_params.insert(idx);
}
}
collect_param_passthroughs(body, source, params, summary);
collect_frees_param_fields(body, source, params, function_macros, summary);
}
fn collect_frees_param_fields(
body: &Node,
source: &str,
params: &[String],
function_macros: &HashMap<String, crate::analyze::macro_expand::FunctionMacro>,
summary: &mut FunctionSummary,
) {
use crate::analyze::macro_expand::macro_nulls_param_indices;
use crate::analyze::points_to::LValue;
use crate::utility::cert_c::ast_utils;
use lang_parsing_substrate::query;
fn flatten(lv: &LValue) -> (String, Vec<String>) {
match lv {
LValue::Var(name) => (name.clone(), Vec::new()),
LValue::Field(base, field) => {
let (root, mut fields) = flatten(base);
fields.push(field.clone());
(root, fields)
}
}
}
fn real_args<'a>(arguments: &Node<'a>) -> Vec<Node<'a>> {
(0..arguments.child_count())
.filter_map(|i| arguments.child(i))
.filter(|a| !matches!(a.kind(), "," | "(" | ")"))
.collect()
}
let credit_field = |summary: &mut FunctionSummary, arg: &Node| {
let Some(lv) = crate::analyze::points_to::lvalue_of(arg, source) else {
return;
};
let (root_name, fields) = flatten(&lv);
if fields.is_empty() {
return;
}
let Some(idx) = params.iter().position(|p| p == &root_name) else {
return;
};
summary
.frees_param_fields
.entry(idx)
.or_default()
.insert(fields.join("->"));
};
for call in query::find_descendants_of_kind(*body, "call_expression") {
let Some(function) = call.child_by_field_name("function") else {
continue;
};
let func_name = function.utf8_text(source.as_bytes()).unwrap_or("");
let Some(arguments) = call.child_by_field_name("arguments") else {
continue;
};
let args = real_args(&arguments);
if func_name == "free" {
for arg in &args {
credit_field(summary, arg);
}
continue;
}
let null_idxs = macro_nulls_param_indices(function_macros, func_name);
if !null_idxs.is_empty() {
for idx in null_idxs {
if let Some(arg) = args.get(idx) {
credit_field(summary, arg);
}
}
continue;
}
if ast_utils::is_deallocation_call_name(func_name) {
for arg in &args {
credit_field(summary, arg);
}
}
}
}
fn body_matches_null_check(body_text: &str, param_name: &str) -> bool {
if !body_text.contains(param_name) {
return false;
}
if contains_word_after_prefix(body_text, "!", param_name) {
return true;
}
for op in ["==", "!="] {
for lit in ["NULL", "0", "nullptr"] {
if contains_word_with_op(body_text, param_name, op, lit) {
return true;
}
if contains_lit_with_op_word(body_text, lit, op, param_name) {
return true;
}
}
}
false
}
fn contains_word_after_prefix(text: &str, prefix: &str, word: &str) -> bool {
let needle = format!("{}{}", prefix, word);
let bytes = text.as_bytes();
let needle_bytes = needle.as_bytes();
let mut start = 0;
while start + needle_bytes.len() <= bytes.len() {
if let Some(pos) = text[start..].find(&needle) {
let absolute = start + pos;
let after = absolute + needle_bytes.len();
let next_is_ident = bytes
.get(after)
.map(|b| is_ident_continue(*b))
.unwrap_or(false);
if !next_is_ident {
return true;
}
start = absolute + 1;
} else {
break;
}
}
false
}
fn contains_word_with_op(text: &str, word: &str, op: &str, lit: &str) -> bool {
let bytes = text.as_bytes();
let word_bytes = word.as_bytes();
let mut start = 0;
while start + word_bytes.len() <= bytes.len() {
let pos = match text[start..].find(word) {
Some(p) => start + p,
None => break,
};
let prev_is_ident = if pos == 0 {
false
} else {
is_ident_continue(bytes[pos - 1])
};
let after = pos + word_bytes.len();
let next_is_ident = bytes
.get(after)
.map(|b| is_ident_continue(*b))
.unwrap_or(false);
if !prev_is_ident && !next_is_ident {
let mut idx = after;
while idx < bytes.len() && (bytes[idx] == b' ' || bytes[idx] == b'\t') {
idx += 1;
}
if bytes[idx..].starts_with(op.as_bytes()) {
idx += op.len();
while idx < bytes.len() && (bytes[idx] == b' ' || bytes[idx] == b'\t') {
idx += 1;
}
if bytes[idx..].starts_with(lit.as_bytes()) {
let lit_end = idx + lit.len();
let next = bytes
.get(lit_end)
.map(|b| is_ident_continue(*b))
.unwrap_or(false);
if !next {
return true;
}
}
}
}
start = pos + 1;
}
false
}
fn contains_lit_with_op_word(text: &str, lit: &str, op: &str, word: &str) -> bool {
let bytes = text.as_bytes();
let lit_bytes = lit.as_bytes();
let mut start = 0;
while start + lit_bytes.len() <= bytes.len() {
let pos = match text[start..].find(lit) {
Some(p) => start + p,
None => break,
};
let prev_is_ident = if pos == 0 {
false
} else {
is_ident_continue(bytes[pos - 1])
};
let after = pos + lit_bytes.len();
let next_is_ident = bytes
.get(after)
.map(|b| is_ident_continue(*b))
.unwrap_or(false);
if !prev_is_ident && !next_is_ident {
let mut idx = after;
while idx < bytes.len() && (bytes[idx] == b' ' || bytes[idx] == b'\t') {
idx += 1;
}
if bytes[idx..].starts_with(op.as_bytes()) {
idx += op.len();
while idx < bytes.len() && (bytes[idx] == b' ' || bytes[idx] == b'\t') {
idx += 1;
}
if bytes[idx..].starts_with(word.as_bytes()) {
let word_end = idx + word.len();
let next = bytes
.get(word_end)
.map(|b| is_ident_continue(*b))
.unwrap_or(false);
if !next {
return true;
}
}
}
}
start = pos + 1;
}
false
}
fn is_ident_continue(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
fn body_matches_alias_null_check(body_text: &str, param_name: &str) -> bool {
let bytes = body_text.as_bytes();
let mut search_from = 0;
while search_from < bytes.len() {
let needle = format!("= {}", param_name);
let pos = match body_text[search_from..].find(&needle) {
Some(p) => search_from + p,
None => break,
};
search_from = pos + 1;
if pos > 0 {
let prev = bytes[pos - 1];
if prev == b'=' || prev == b'!' || prev == b'<' || prev == b'>' {
continue;
}
}
let after = pos + needle.len();
let next = bytes.get(after).copied().unwrap_or(0);
if is_ident_continue(next) {
continue;
}
if next != b';' && next != b',' && next != b')' && !next.is_ascii_whitespace() {
continue;
}
let mut end = pos;
while end > 0 && (bytes[end - 1] == b' ' || bytes[end - 1] == b'\t') {
end -= 1;
}
let lhs_end = end;
while end > 0 && is_ident_continue(bytes[end - 1]) {
end -= 1;
}
let lhs_start = end;
if lhs_start >= lhs_end {
continue;
}
let alias = &body_text[lhs_start..lhs_end];
if alias == param_name {
continue;
}
if !matches!(alias.as_bytes()[0], b'a'..=b'z' | b'A'..=b'Z' | b'_') {
continue;
}
if body_matches_null_check(body_text, alias) {
return true;
}
}
false
}
fn collect_param_passthroughs(
node: &Node,
source: &str,
params: &[String],
summary: &mut FunctionSummary,
) {
if node.kind() == "call_expression" {
if let Some(func_node) = node.child_by_field_name("function") {
let callee_name = func_node
.utf8_text(source.as_bytes())
.unwrap_or("")
.to_string();
if !callee_name.is_empty() && callee_name != "free" && callee_name != "realloc" {
if let Some(arguments) = node.child_by_field_name("arguments") {
let mut callee_idx = 0usize;
for i in 0..arguments.child_count() {
if let Some(arg) = arguments.child(i) {
if arg.kind() == "," || arg.kind() == "(" || arg.kind() == ")" {
continue;
}
if arg.kind() == "identifier" {
let arg_text = arg.utf8_text(source.as_bytes()).unwrap_or("");
for (param_idx, param_name) in params.iter().enumerate() {
if !param_name.is_empty() && arg_text == param_name {
summary
.param_passthroughs
.entry(param_idx)
.or_default()
.push((callee_name.clone(), callee_idx));
}
}
}
callee_idx += 1;
}
}
}
}
}
return; }
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if is_real_nested_function_definition(&child, source) {
continue;
}
collect_param_passthroughs(&child, source, params, summary);
}
}
}
pub fn propagate_transitive_frees(summaries: &mut HashMap<String, FunctionSummary>) {
for _pass in 0..10 {
let mut changed = false;
let frees_snapshot: HashMap<String, HashSet<usize>> = summaries
.iter()
.map(|(n, s)| (n.clone(), s.frees_params.clone()))
.collect();
for summary in summaries.values_mut() {
for (caller_idx, callees) in &summary.param_passthroughs {
for (callee_name, callee_idx) in callees {
if let Some(callee_frees) = frees_snapshot.get(callee_name) {
if callee_frees.contains(callee_idx)
&& !summary.frees_params.contains(caller_idx)
{
summary.frees_params.insert(*caller_idx);
changed = true;
}
}
}
}
}
if !changed {
break;
}
}
}
pub fn propagate_transitive_frees_param_fields(summaries: &mut HashMap<String, FunctionSummary>) {
for _pass in 0..10 {
let mut changed = false;
let snapshot: HashMap<String, HashMap<usize, HashSet<String>>> = summaries
.iter()
.map(|(n, s)| (n.clone(), s.frees_param_fields.clone()))
.collect();
for summary in summaries.values_mut() {
for (caller_idx, callees) in &summary.param_passthroughs {
for (callee_name, callee_idx) in callees {
let Some(callee_fields) =
snapshot.get(callee_name).and_then(|m| m.get(callee_idx))
else {
continue;
};
let entry = summary.frees_param_fields.entry(*caller_idx).or_default();
for field in callee_fields {
if entry.insert(field.clone()) {
changed = true;
}
}
}
}
}
if !changed {
break;
}
}
}
fn collect_returns_from_callees(body: &Node, source: &str, out: &mut HashSet<String>) {
let mut returns = Vec::new();
collect_return_expressions(body, source, &mut returns);
for ret in returns {
let inner = unwrap_to_call_node(ret);
if inner.kind() == "call_expression" {
if let Some(func) = inner.child_by_field_name("function") {
let name = func.utf8_text(source.as_bytes()).unwrap_or("");
let ident = name
.rsplit(|c: char| !c.is_alphanumeric() && c != '_')
.next()
.unwrap_or(name);
if !ident.is_empty() {
out.insert(ident.to_string());
}
}
}
}
}
fn unwrap_to_call_node<'a>(mut node: Node<'a>) -> Node<'a> {
loop {
match node.kind() {
"parenthesized_expression" => {
if let Some(inner) = node.named_child(0) {
node = inner;
continue;
}
break;
}
"cast_expression" => {
if let Some(value) = node.child_by_field_name("value") {
node = value;
continue;
}
break;
}
_ => break,
}
}
node
}
pub fn propagate_return_taint(summaries: &mut HashMap<String, FunctionSummary>) {
for _pass in 0..10 {
let mut changed = false;
let snapshot: HashMap<String, bool> = summaries
.iter()
.map(|(n, s)| (n.clone(), s.returns_tainted))
.collect();
for summary in summaries.values_mut() {
if summary.returns_tainted {
continue;
}
for callee in &summary.returns_from_callees {
if let Some(&callee_tainted) = snapshot.get(callee) {
if callee_tainted {
summary.returns_tainted = true;
changed = true;
break;
}
}
}
}
if !changed {
break;
}
}
}
fn compute_return_range(
body: &Node,
source: &str,
macros: &MacroConstantMap,
) -> Option<ValueRange> {
let mut return_exprs = Vec::new();
collect_return_expressions(body, source, &mut return_exprs);
if return_exprs.is_empty() {
return None;
}
let empty_vars = VarRangeMap::new();
let mut combined: Option<ValueRange> = None;
for expr_node in &return_exprs {
let range = const_eval::try_evaluate_range(expr_node, source, macros, &empty_vars)?;
combined = Some(match combined {
Some(existing) => {
ValueRange::new(existing.min.min(range.min), existing.max.max(range.max))
}
None => range,
});
}
combined
}
fn collect_return_expressions<'a>(node: &Node<'a>, source: &str, out: &mut Vec<Node<'a>>) {
if node.kind() == "return_statement" {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() != "return" && child.kind() != ";" {
out.push(child);
return;
}
}
}
return;
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if is_real_nested_function_definition(&child, source) {
continue;
}
collect_return_expressions(&child, source, out);
}
}
}
pub fn extract_function_name(func_node: &Node, source: &str) -> Option<String> {
let declarator = func_node.child_by_field_name("declarator")?;
let name = extract_leaf_identifier(&declarator, source);
if name.is_empty() {
None
} else {
Some(name)
}
}
fn extract_leaf_identifier(node: &Node, source: &str) -> String {
match node.kind() {
"identifier" => node.utf8_text(source.as_bytes()).unwrap_or("").to_string(),
"function_declarator" | "pointer_declarator" | "array_declarator" => {
if let Some(inner) = node.child_by_field_name("declarator") {
extract_leaf_identifier(&inner, source)
} else {
String::new()
}
}
_ => {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
return child.utf8_text(source.as_bytes()).unwrap_or("").to_string();
}
}
}
String::new()
}
}
}
pub fn infer_arg_null_state(arg: &Node, source: &str) -> NullState {
match arg.kind() {
"null" | "nullptr" => NullState::DefinitelyNull,
"number_literal" => {
let text = arg.utf8_text(source.as_bytes()).unwrap_or("").trim();
if text == "0" {
NullState::DefinitelyNull
} else {
NullState::NotNull
}
}
"string_literal" | "concatenated_string" | "char_literal" => NullState::NotNull,
"unary_expression" => {
if let Some(op) = arg.child_by_field_name("operator") {
if op.utf8_text(source.as_bytes()).unwrap_or("") == "&" {
return NullState::NotNull;
}
}
NullState::Unknown
}
"pointer_expression" => {
if let Some(op) = arg.child_by_field_name("operator") {
if op.utf8_text(source.as_bytes()).unwrap_or("") == "&" {
return NullState::NotNull;
}
}
NullState::Unknown
}
"cast_expression" => {
if let Some(value) = arg.child_by_field_name("value") {
let inner = infer_arg_null_state(&value, source);
if inner == NullState::DefinitelyNull {
return NullState::DefinitelyNull;
}
}
NullState::Unknown
}
"parenthesized_expression" => {
if let Some(inner) = arg.child(1) {
return infer_arg_null_state(&inner, source);
}
NullState::Unknown
}
"identifier" => {
let text = arg.utf8_text(source.as_bytes()).unwrap_or("");
if text == "NULL" {
NullState::DefinitelyNull
} else if matches!(text, "stdout" | "stderr" | "stdin") {
NullState::NotNull
} else {
NullState::Unknown
}
}
_ => NullState::Unknown,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_and_summarize(code: &str) -> HashMap<String, FunctionSummary> {
let mut parser = tree_sitter::Parser::new();
parser.set_language(&crate::parser::c_language()).unwrap();
let tree = parser.parse(code, None).unwrap();
let macros = const_eval::collect_macro_constants(&tree.root_node(), code);
compute_summaries(
&tree.root_node(),
code,
¯os,
true,
&[],
&HashMap::new(),
&HashMap::new(),
)
}
#[test]
fn test_never_returns() {
let code = r#"
void die(const char *msg) {
fprintf(stderr, "%s\n", msg);
abort();
}
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("die").unwrap();
assert!(summary.never_returns);
}
#[test]
fn test_frees_params() {
let code = r#"
void cleanup(void *ptr) {
free(ptr);
}
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("cleanup").unwrap();
assert!(summary.frees_params.contains(&0));
}
#[test]
fn test_can_return_null() {
let code = r#"
char *find_match(const char *haystack, const char *needle) {
char *result = strstr(haystack, needle);
if (!result) {
return NULL;
}
return result;
}
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("find_match").unwrap();
assert!(summary.can_return_null);
}
#[test]
fn test_checks_null_params() {
let code = r#"
int safe_strlen(const char *s) {
if (s == NULL) {
return 0;
}
return strlen(s);
}
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("safe_strlen").unwrap();
assert!(summary.checks_null_params.contains(&0));
}
#[test]
fn test_modifies_params() {
let code = r#"
void init_struct(struct config *cfg) {
cfg->value = 0;
cfg->name = "default";
}
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("init_struct").unwrap();
assert!(summary.modifies_params.contains(&0));
}
#[test]
fn test_returns_allocation() {
let code = r#"
char *create_buffer(size_t size) {
char *buf = malloc(size);
if (!buf) return NULL;
memset(buf, 0, size);
return buf;
}
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("create_buffer").unwrap();
assert!(summary.returns_allocation);
assert!(summary.can_return_null);
}
#[test]
fn test_return_range_constant() {
let code = r#"
int get_five(void) { return 5; }
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("get_five").unwrap();
assert_eq!(summary.return_range, Some(ValueRange::exact(5)));
}
#[test]
fn test_return_range_multiple_paths() {
let code = r#"
int get_bounded(int flag) {
if (flag) return 1;
return 10;
}
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("get_bounded").unwrap();
assert_eq!(summary.return_range, Some(ValueRange::new(1, 10)));
}
#[test]
fn test_return_range_void() {
let code = r#"
void do_nothing(void) { return; }
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("do_nothing").unwrap();
assert_eq!(summary.return_range, None);
}
#[test]
fn test_return_range_pointer() {
let code = r#"
int *get_ptr(void) { return 0; }
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("get_ptr").unwrap();
assert_eq!(summary.return_range, None);
}
#[test]
fn test_return_range_param_dependent() {
let code = r#"
int identity(int x) { return x; }
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("identity").unwrap();
assert_eq!(summary.return_range, None);
}
#[test]
fn test_return_range_macro() {
let code = r#"
#define MAX_COUNT 100
int get_max(void) { return MAX_COUNT; }
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("get_max").unwrap();
assert_eq!(summary.return_range, Some(ValueRange::exact(100)));
}
#[test]
fn test_return_range_zero() {
let code = r#"
int get_zero(void) { return 0; }
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("get_zero").unwrap();
assert_eq!(summary.return_range, Some(ValueRange::exact(0)));
}
#[test]
fn test_return_range_negative() {
let code = r#"
int get_error(void) { return -1; }
"#;
let summaries = parse_and_summarize(code);
let summary = summaries.get("get_error").unwrap();
assert_eq!(summary.return_range, Some(ValueRange::exact(-1)));
}
#[test]
fn test_ifdef_spanning_brace_does_not_swallow_sibling_summary() {
let code = r#"int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFlags){
int rc;
int i;
#ifndef SQLITE_OMIT_DEPRECATED
int size;
#endif
Db *pDb;
char const *azArg[6];
int meta[5];
InitData initData;
const char *zSchemaTabName;
int openedTransaction = 0;
int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed);
assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 );
assert( iDb>=0 && iDb<db->nDb );
assert( db->aDb[iDb].pSchema );
assert( sqlite3_mutex_held(db->mutex) );
assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
db->init.busy = 1;
/* Construct the in-memory representation schema tables (sqlite_schema or
** sqlite_temp_schema) by invoking the parser directly. The appropriate
** table name will be inserted automatically by the parser so we can just
** use the abbreviation "x" here. The parser will also automatically tag
** the schema table as read-only. */
azArg[0] = "table";
azArg[1] = zSchemaTabName = SCHEMA_TABLE(iDb);
azArg[2] = azArg[1];
azArg[3] = "1";
azArg[4] = "CREATE TABLE x(type text,name text,tbl_name text,"
"rootpage int,sql text)";
azArg[5] = 0;
initData.db = db;
initData.iDb = iDb;
initData.rc = SQLITE_OK;
initData.pzErrMsg = pzErrMsg;
initData.mInitFlags = mFlags;
initData.nInitRow = 0;
initData.mxPage = 0;
sqlite3InitCallback(&initData, 5, (char **)azArg, 0);
db->mDbFlags &= mask;
if( initData.rc ){
rc = initData.rc;
goto error_out;
}
/* Create a cursor to hold the database open
*/
pDb = &db->aDb[iDb];
if( pDb->pBt==0 ){
assert( iDb==1 );
DbSetProperty(db, 1, DB_SchemaLoaded);
rc = SQLITE_OK;
goto error_out;
}
/* If there is not already a read-only (or read-write) transaction opened
** on the b-tree database, open one now. If a transaction is opened, it
** will be closed before this function returns. */
sqlite3BtreeEnter(pDb->pBt);
if( sqlite3BtreeTxnState(pDb->pBt)==SQLITE_TXN_NONE ){
rc = sqlite3BtreeBeginTrans(pDb->pBt, 0, 0);
if( rc!=SQLITE_OK ){
sqlite3SetString(pzErrMsg, db, sqlite3ErrStr(rc));
goto initone_error_out;
}
openedTransaction = 1;
}
/* Get the database meta information.
**
** Meta values are as follows:
** meta[0] Schema cookie. Changes with each schema change.
** meta[1] File format of schema layer.
** meta[2] Size of the page cache.
** meta[3] Largest rootpage (auto/incr_vacuum mode)
** meta[4] Db text encoding. 1:UTF-8 2:UTF-16LE 3:UTF-16BE
** meta[5] User version
** meta[6] Incremental vacuum mode
** meta[7] unused
** meta[8] unused
** meta[9] unused
**
** Note: The #defined SQLITE_UTF* symbols in sqliteInt.h correspond to
** the possible values of meta[4].
*/
for(i=0; i<ArraySize(meta); i++){
sqlite3BtreeGetMeta(pDb->pBt, i+1, (u32 *)&meta[i]);
}
if( (db->flags & SQLITE_ResetDatabase)!=0 ){
memset(meta, 0, sizeof(meta));
}
pDb->pSchema->schema_cookie = meta[BTREE_SCHEMA_VERSION-1];
/* If opening a non-empty database, check the text encoding. For the
** main database, set sqlite3.enc to the encoding of the main database.
** For an attached db, it is an error if the encoding is not the same
** as sqlite3.enc.
*/
if( meta[BTREE_TEXT_ENCODING-1] ){ /* text encoding */
if( iDb==0 && (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){
u8 encoding;
#ifndef SQLITE_OMIT_UTF16
/* If opening the main database, set ENC(db). */
encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3;
if( encoding==0 ) encoding = SQLITE_UTF8;
#else
encoding = SQLITE_UTF8;
#endif
sqlite3SetTextEncoding(db, encoding);
}else{
/* If opening an attached database, the encoding much match ENC(db) */
if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){
sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
" text encoding as main database");
rc = SQLITE_ERROR;
goto initone_error_out;
}
}
}
pDb->pSchema->enc = ENC(db);
if( pDb->pSchema->cache_size==0 ){
#ifndef SQLITE_OMIT_DEPRECATED
size = sqlite3AbsInt32(meta[BTREE_DEFAULT_CACHE_SIZE-1]);
if( size==0 ){ size = SQLITE_DEFAULT_CACHE_SIZE; }
pDb->pSchema->cache_size = size;
#else
pDb->pSchema->cache_size = SQLITE_DEFAULT_CACHE_SIZE;
#endif
sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
}
/*
** file_format==1 Version 3.0.0.
** file_format==2 Version 3.1.3. // ALTER TABLE ADD COLUMN
** file_format==3 Version 3.1.4. // ditto but with non-NULL defaults
** file_format==4 Version 3.3.0. // DESC indices. Boolean constants
*/
pDb->pSchema->file_format = (u8)meta[BTREE_FILE_FORMAT-1];
if( pDb->pSchema->file_format==0 ){
pDb->pSchema->file_format = 1;
}
if( pDb->pSchema->file_format>SQLITE_MAX_FILE_FORMAT ){
sqlite3SetString(pzErrMsg, db, "unsupported file format");
rc = SQLITE_ERROR;
goto initone_error_out;
}
/* Ticket #2804: When we open a database in the newer file format,
** clear the legacy_file_format pragma flag so that a VACUUM will
** not downgrade the database and thus invalidate any descending
** indices that the user might have created.
*/
if( iDb==0 && meta[BTREE_FILE_FORMAT-1]>=4 ){
db->flags &= ~(u64)SQLITE_LegacyFileFmt;
}
/* Read the schema information out of the schema tables
*/
assert( db->init.busy );
initData.mxPage = sqlite3BtreeLastPage(pDb->pBt);
{
char *zSql;
zSql = sqlite3MPrintf(db,
"SELECT*FROM\"%w\".%s ORDER BY rowid",
db->aDb[iDb].zDbSName, zSchemaTabName);
#ifndef SQLITE_OMIT_AUTHORIZATION
{
sqlite3_xauth xAuth;
xAuth = db->xAuth;
db->xAuth = 0;
#endif
rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
#ifndef SQLITE_OMIT_AUTHORIZATION
db->xAuth = xAuth;
}
#endif
if( rc==SQLITE_OK ) rc = initData.rc;
sqlite3DbFree(db, zSql);
#ifndef SQLITE_OMIT_ANALYZE
if( rc==SQLITE_OK ){
sqlite3AnalysisLoad(db, iDb);
}
#endif
}
assert( pDb == &(db->aDb[iDb]) );
if( db->mallocFailed ){
rc = SQLITE_NOMEM_BKPT;
sqlite3ResetAllSchemasOfConnection(db);
pDb = &db->aDb[iDb];
}else
if( rc==SQLITE_OK || ((db->flags&SQLITE_NoSchemaError) && rc!=SQLITE_NOMEM)){
/* Hack: If the SQLITE_NoSchemaError flag is set, then consider
** the schema loaded, even if errors (other than OOM) occurred. In
** this situation the current sqlite3_prepare() operation will fail,
** but the following one will attempt to compile the supplied statement
** against whatever subset of the schema was loaded before the error
** occurred.
**
** The primary purpose of this is to allow access to the sqlite_schema
** table even when its contents have been corrupted.
*/
DbSetProperty(db, iDb, DB_SchemaLoaded);
rc = SQLITE_OK;
}
/* Jump here for an error that occurs after successfully allocating
** curMain and calling sqlite3BtreeEnter(). For an error that occurs
** before that point, jump to error_out.
*/
initone_error_out:
if( openedTransaction ){
sqlite3BtreeCommit(pDb->pBt);
}
sqlite3BtreeLeave(pDb->pBt);
error_out:
if( rc ){
if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
sqlite3OomFault(db);
}
sqlite3ResetOneSchema(db, iDb);
}
db->init.busy = 0;
return rc;
}
/*
** Initialize all database files - the main database file, the file
** used to store temporary tables, and any additional database files
** created using ATTACH statements. Return a success code. If an
** error occurs, write an error message into *pzErrMsg.
**
** After a database is initialized, the DB_SchemaLoaded bit is set
** bit is set in the flags field of the Db structure.
*/
int sqlite3Init(sqlite3 *db, char **pzErrMsg){
int i, rc;
int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange);
assert( sqlite3_mutex_held(db->mutex) );
assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) );
assert( db->init.busy==0 );
ENC(db) = SCHEMA_ENC(db);
assert( db->nDb>0 );
/* Do the main schema first */
if( !DbHasProperty(db, 0, DB_SchemaLoaded) ){
rc = sqlite3InitOne(db, 0, pzErrMsg, 0);
if( rc ) return rc;
}
/* All other schemas after the main schema. The "temp" schema must be last */
for(i=db->nDb-1; i>0; i--){
assert( i==1 || sqlite3BtreeHoldsMutex(db->aDb[i].pBt) );
if( !DbHasProperty(db, i, DB_SchemaLoaded) ){
rc = sqlite3InitOne(db, i, pzErrMsg, 0);
if( rc ) return rc;
}
}
if( commit_internal ){
sqlite3CommitInternalChanges(db);
}
return SQLITE_OK;
}"#;
let summaries = parse_and_summarize(code);
assert!(
summaries.contains_key("sqlite3Init"),
"sqlite3Init was swallowed into sqlite3InitOne's corrupted span \
and got no summary entry of its own: {:?}",
summaries.keys().collect::<Vec<_>>()
);
assert!(summaries.contains_key("sqlite3InitOne"));
}
#[test]
fn test_ifdef_spanning_brace_does_not_contaminate_outer_summary() {
let code = r#"int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFlags){
int rc;
int i;
#ifndef SQLITE_OMIT_DEPRECATED
int size;
#endif
Db *pDb;
char const *azArg[6];
int meta[5];
InitData initData;
const char *zSchemaTabName;
int openedTransaction = 0;
int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed);
assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 );
assert( iDb>=0 && iDb<db->nDb );
assert( db->aDb[iDb].pSchema );
assert( sqlite3_mutex_held(db->mutex) );
assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
db->init.busy = 1;
/* Construct the in-memory representation schema tables (sqlite_schema or
** sqlite_temp_schema) by invoking the parser directly. The appropriate
** table name will be inserted automatically by the parser so we can just
** use the abbreviation "x" here. The parser will also automatically tag
** the schema table as read-only. */
azArg[0] = "table";
azArg[1] = zSchemaTabName = SCHEMA_TABLE(iDb);
azArg[2] = azArg[1];
azArg[3] = "1";
azArg[4] = "CREATE TABLE x(type text,name text,tbl_name text,"
"rootpage int,sql text)";
azArg[5] = 0;
initData.db = db;
initData.iDb = iDb;
initData.rc = SQLITE_OK;
initData.pzErrMsg = pzErrMsg;
initData.mInitFlags = mFlags;
initData.nInitRow = 0;
initData.mxPage = 0;
sqlite3InitCallback(&initData, 5, (char **)azArg, 0);
db->mDbFlags &= mask;
if( initData.rc ){
rc = initData.rc;
goto error_out;
}
/* Create a cursor to hold the database open
*/
pDb = &db->aDb[iDb];
if( pDb->pBt==0 ){
assert( iDb==1 );
DbSetProperty(db, 1, DB_SchemaLoaded);
rc = SQLITE_OK;
goto error_out;
}
/* If there is not already a read-only (or read-write) transaction opened
** on the b-tree database, open one now. If a transaction is opened, it
** will be closed before this function returns. */
sqlite3BtreeEnter(pDb->pBt);
if( sqlite3BtreeTxnState(pDb->pBt)==SQLITE_TXN_NONE ){
rc = sqlite3BtreeBeginTrans(pDb->pBt, 0, 0);
if( rc!=SQLITE_OK ){
sqlite3SetString(pzErrMsg, db, sqlite3ErrStr(rc));
goto initone_error_out;
}
openedTransaction = 1;
}
/* Get the database meta information.
**
** Meta values are as follows:
** meta[0] Schema cookie. Changes with each schema change.
** meta[1] File format of schema layer.
** meta[2] Size of the page cache.
** meta[3] Largest rootpage (auto/incr_vacuum mode)
** meta[4] Db text encoding. 1:UTF-8 2:UTF-16LE 3:UTF-16BE
** meta[5] User version
** meta[6] Incremental vacuum mode
** meta[7] unused
** meta[8] unused
** meta[9] unused
**
** Note: The #defined SQLITE_UTF* symbols in sqliteInt.h correspond to
** the possible values of meta[4].
*/
for(i=0; i<ArraySize(meta); i++){
sqlite3BtreeGetMeta(pDb->pBt, i+1, (u32 *)&meta[i]);
}
if( (db->flags & SQLITE_ResetDatabase)!=0 ){
memset(meta, 0, sizeof(meta));
}
pDb->pSchema->schema_cookie = meta[BTREE_SCHEMA_VERSION-1];
/* If opening a non-empty database, check the text encoding. For the
** main database, set sqlite3.enc to the encoding of the main database.
** For an attached db, it is an error if the encoding is not the same
** as sqlite3.enc.
*/
if( meta[BTREE_TEXT_ENCODING-1] ){ /* text encoding */
if( iDb==0 && (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){
u8 encoding;
#ifndef SQLITE_OMIT_UTF16
/* If opening the main database, set ENC(db). */
encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3;
if( encoding==0 ) encoding = SQLITE_UTF8;
#else
encoding = SQLITE_UTF8;
#endif
sqlite3SetTextEncoding(db, encoding);
}else{
/* If opening an attached database, the encoding much match ENC(db) */
if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){
sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
" text encoding as main database");
rc = SQLITE_ERROR;
goto initone_error_out;
}
}
}
pDb->pSchema->enc = ENC(db);
if( pDb->pSchema->cache_size==0 ){
#ifndef SQLITE_OMIT_DEPRECATED
size = sqlite3AbsInt32(meta[BTREE_DEFAULT_CACHE_SIZE-1]);
if( size==0 ){ size = SQLITE_DEFAULT_CACHE_SIZE; }
pDb->pSchema->cache_size = size;
#else
pDb->pSchema->cache_size = SQLITE_DEFAULT_CACHE_SIZE;
#endif
sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
}
/*
** file_format==1 Version 3.0.0.
** file_format==2 Version 3.1.3. // ALTER TABLE ADD COLUMN
** file_format==3 Version 3.1.4. // ditto but with non-NULL defaults
** file_format==4 Version 3.3.0. // DESC indices. Boolean constants
*/
pDb->pSchema->file_format = (u8)meta[BTREE_FILE_FORMAT-1];
if( pDb->pSchema->file_format==0 ){
pDb->pSchema->file_format = 1;
}
if( pDb->pSchema->file_format>SQLITE_MAX_FILE_FORMAT ){
sqlite3SetString(pzErrMsg, db, "unsupported file format");
rc = SQLITE_ERROR;
goto initone_error_out;
}
/* Ticket #2804: When we open a database in the newer file format,
** clear the legacy_file_format pragma flag so that a VACUUM will
** not downgrade the database and thus invalidate any descending
** indices that the user might have created.
*/
if( iDb==0 && meta[BTREE_FILE_FORMAT-1]>=4 ){
db->flags &= ~(u64)SQLITE_LegacyFileFmt;
}
/* Read the schema information out of the schema tables
*/
assert( db->init.busy );
initData.mxPage = sqlite3BtreeLastPage(pDb->pBt);
{
char *zSql;
zSql = sqlite3MPrintf(db,
"SELECT*FROM\"%w\".%s ORDER BY rowid",
db->aDb[iDb].zDbSName, zSchemaTabName);
#ifndef SQLITE_OMIT_AUTHORIZATION
{
sqlite3_xauth xAuth;
xAuth = db->xAuth;
db->xAuth = 0;
#endif
rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
#ifndef SQLITE_OMIT_AUTHORIZATION
db->xAuth = xAuth;
}
#endif
if( rc==SQLITE_OK ) rc = initData.rc;
sqlite3DbFree(db, zSql);
#ifndef SQLITE_OMIT_ANALYZE
if( rc==SQLITE_OK ){
sqlite3AnalysisLoad(db, iDb);
}
#endif
}
assert( pDb == &(db->aDb[iDb]) );
if( db->mallocFailed ){
rc = SQLITE_NOMEM_BKPT;
sqlite3ResetAllSchemasOfConnection(db);
pDb = &db->aDb[iDb];
}else
if( rc==SQLITE_OK || ((db->flags&SQLITE_NoSchemaError) && rc!=SQLITE_NOMEM)){
/* Hack: If the SQLITE_NoSchemaError flag is set, then consider
** the schema loaded, even if errors (other than OOM) occurred. In
** this situation the current sqlite3_prepare() operation will fail,
** but the following one will attempt to compile the supplied statement
** against whatever subset of the schema was loaded before the error
** occurred.
**
** The primary purpose of this is to allow access to the sqlite_schema
** table even when its contents have been corrupted.
*/
DbSetProperty(db, iDb, DB_SchemaLoaded);
rc = SQLITE_OK;
}
/* Jump here for an error that occurs after successfully allocating
** curMain and calling sqlite3BtreeEnter(). For an error that occurs
** before that point, jump to error_out.
*/
initone_error_out:
if( openedTransaction ){
sqlite3BtreeCommit(pDb->pBt);
}
sqlite3BtreeLeave(pDb->pBt);
error_out:
if( rc ){
if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
sqlite3OomFault(db);
}
sqlite3ResetOneSchema(db, iDb);
}
db->init.busy = 0;
return rc;
}
/*
** Initialize all database files - the main database file, the file
** used to store temporary tables, and any additional database files
** created using ATTACH statements. Return a success code. If an
** error occurs, write an error message into *pzErrMsg.
**
** After a database is initialized, the DB_SchemaLoaded bit is set
** bit is set in the flags field of the Db structure.
*/
int sqlite3Init(sqlite3 *db, char **pzErrMsg){
int i, rc;
int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange);
assert( sqlite3_mutex_held(db->mutex) );
assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) );
assert( db->init.busy==0 );
ENC(db) = SCHEMA_ENC(db);
assert( db->nDb>0 );
/* Do the main schema first */
if( !DbHasProperty(db, 0, DB_SchemaLoaded) ){
rc = sqlite3InitOne(db, 0, pzErrMsg, 0);
if( rc ) return rc;
}
/* All other schemas after the main schema. The "temp" schema must be last */
for(i=db->nDb-1; i>0; i--){
assert( i==1 || sqlite3BtreeHoldsMutex(db->aDb[i].pBt) );
if( !DbHasProperty(db, i, DB_SchemaLoaded) ){
rc = sqlite3InitOne(db, i, pzErrMsg, 0);
if( rc ) return rc;
}
}
if( commit_internal ){
sqlite3CommitInternalChanges(db);
}
return SQLITE_OK;
}
void extra_leak_marker(void){
void *buf = malloc(4);
free(buf);
abort();
}"#;
let summaries = parse_and_summarize(code);
let outer = summaries
.get("sqlite3InitOne")
.expect("sqlite3InitOne should still get its own summary");
assert!(
!outer.returns_allocation,
"sqlite3InitOne's summary was contaminated with extra_leak_marker's malloc() call"
);
assert!(
!outer.never_returns,
"sqlite3InitOne's summary was contaminated with extra_leak_marker's abort() call"
);
assert!(summaries.contains_key("sqlite3Init"));
let marker = summaries
.get("extra_leak_marker")
.expect("extra_leak_marker should get its own summary entry");
assert!(marker.returns_allocation);
assert!(marker.never_returns);
}
}