Skip to content

feat: add index out of bounds check #442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/ast/builder/llvmbuilder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,20 @@
let f = self.get_llvm_value(f).unwrap().into_function_value();
f.get_name().to_str().unwrap() == "main"
}

fn build_global_string_ptr(&self, s: &str, name: &str) -> ValueHandle {
let s = self.builder.build_global_string_ptr(s, name).unwrap();
self.get_llvm_value_handle(&s.as_any_value_enum())
}

Check warning on line 1758 in src/ast/builder/llvmbuilder.rs

View check run for this annotation

Codecov / codecov/patch

src/ast/builder/llvmbuilder.rs#L1755-L1758

Added lines #L1755 - L1758 were not covered by tests

fn build_unreachable(&self) {
_ = self.builder.build_unreachable();
}

fn is_debug(&self) -> bool {
self.debug
}

Check warning on line 1766 in src/ast/builder/llvmbuilder.rs

View check run for this annotation

Codecov / codecov/patch

src/ast/builder/llvmbuilder.rs#L1764-L1766

Added lines #L1764 - L1766 were not covered by tests

fn tag_generator_ctx_as_root(&self, f: ValueHandle, ctx: &mut Ctx<'a>) {
let f = self.get_llvm_value(f).unwrap().into_function_value();
let allocab = f.get_first_basic_block().unwrap();
Expand Down Expand Up @@ -2371,7 +2385,7 @@
if *self.optimized.borrow() {
return;
}
if !self.debug {
if !self.debug && self.optlevel as u32 >= 1 {

Check warning on line 2388 in src/ast/builder/llvmbuilder.rs

View check run for this annotation

Codecov / codecov/patch

src/ast/builder/llvmbuilder.rs#L2388

Added line #L2388 was not covered by tests
self.module.strip_debug_info();
}
self.module.verify().unwrap_or_else(|e| {
Expand Down
3 changes: 3 additions & 0 deletions src/ast/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub trait IRBuilder<'a, 'ctx> {
fn get_global_var_handle(&self, name: &str) -> Option<ValueHandle>;
fn new_subscope(&self, start: Pos);
fn get_sp_handle(&self) -> ValueHandle;
fn is_debug(&self) -> bool;
fn add_global(
&self,
name: &str,
Expand Down Expand Up @@ -329,6 +330,8 @@ pub trait IRBuilder<'a, 'ctx> {
fn is_main(&self, f: ValueHandle) -> bool;
fn await_task(&self, ctx: &mut Ctx<'a>, task: ValueHandle) -> ValueHandle;
fn await_ret(&self, ctx: &mut Ctx<'a>, ret: ValueHandle);
fn build_global_string_ptr(&self, s: &str, name: &str) -> ValueHandle;
fn build_unreachable(&self);
}

/// ValueHandle is an index used to separate the low level generatted code inside [BuilderEnum] from the respective high level ast node
Expand Down
12 changes: 12 additions & 0 deletions src/ast/builder/no_op_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,4 +631,16 @@
0
}
fn await_ret(&self, _ctx: &mut Ctx<'a>, _ret: ValueHandle) {}

fn is_debug(&self) -> bool {
false
}

Check warning on line 637 in src/ast/builder/no_op_builder.rs

View check run for this annotation

Codecov / codecov/patch

src/ast/builder/no_op_builder.rs#L635-L637

Added lines #L635 - L637 were not covered by tests

fn build_global_string_ptr(&self, _s: &str, _name: &str) -> ValueHandle {
0
}

Check warning on line 641 in src/ast/builder/no_op_builder.rs

View check run for this annotation

Codecov / codecov/patch

src/ast/builder/no_op_builder.rs#L639-L641

Added lines #L639 - L641 were not covered by tests

fn build_unreachable(&self) {
// 什么都不做
}
}
65 changes: 65 additions & 0 deletions src/ast/node/primary.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::cell::RefCell;
use std::sync::Arc;

use super::node_result::NodeResultBuilder;
use super::*;

use crate::ast::builder::BuilderEnum;
use crate::ast::builder::IRBuilder;
use crate::ast::builder::IntPredicate;
use crate::ast::builder::ValueHandle;
use crate::ast::ctx::Ctx;
use crate::ast::ctx::MacroReplaceNode;
Expand Down Expand Up @@ -366,6 +368,69 @@ impl Node for ArrayElementNode {

let elemptr: ValueHandle = {
let index: &[ValueHandle; 1] = &[index_val];

// 在debug模式下添加越界检查
if ctx.config.assert_index_out_of_bounds {
// 获取数组大小
let size_ptr = builder
.build_struct_gep(arr, 2, "size_ptr", &pltype.borrow(), ctx)
.unwrap();
let size = builder.build_load(
size_ptr,
"arr_size",
&PLType::Primitive(PriType::I64),
ctx,
);

// 创建比较:index >= size 或 index < 0
let cmp_ge = builder.build_int_compare(
IntPredicate::SGE,
index_val,
size,
"index_ge_size",
);
let cmp_lt = builder.build_int_compare(
IntPredicate::SLT,
index_val,
builder.int_value(&PriType::I64, 0, true),
"index_lt_zero",
);
let out_of_bounds = builder.build_or(cmp_ge, cmp_lt, "out_of_bounds");

// 获取当前函数
let current_func = ctx.function.unwrap();
let error_block = builder.append_basic_block(current_func, "arr_index_error");
let continue_block =
builder.append_basic_block(current_func, "arr_index_continue");

builder.build_conditional_branch(out_of_bounds, error_block, continue_block);

// 错误处理块
builder.position_at_end_block(error_block);

// 获取printf函数
let printf_fn = builder
.get_function("pl_index_out_of_bounds")
.unwrap_or_else(|| {
let ret_type = PLType::Void;
let param_type = PLType::Primitive(PriType::I64);
builder.add_function(
"pl_index_out_of_bounds",
&[param_type.clone(), param_type],
ret_type,
ctx,
)
});

// 调用printf输出错误信息
builder.build_call(printf_fn, &[index_val, size], &PLType::Void, ctx, None);

builder.build_unreachable();

// 继续执行块
builder.position_at_end_block(continue_block);
}

let real_arr: ValueHandle = builder
.build_struct_gep(arr, 1, "real_arr", &pltype.borrow(), ctx)
.unwrap();
Expand Down
52 changes: 52 additions & 0 deletions src/ast/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,58 @@ fn test_tail_call_opt() {
drop(l);
}

#[test]
fn test_assert_index_out_of_bounds() {
let l = crate::utils::plc_new::tests::TEST_COMPILE_MUTEX
.lock()
.unwrap();
set_test_asset();
let out = "testout3";
let exe = PathBuf::from(out);
#[cfg(target_os = "windows")]
let exe = exe.with_extension("exe");
_ = remove_file(&exe);
use std::{path::PathBuf, process::Command};

use crate::ast::compiler::{compile, Options};

let docs = MemDocs::default();
let db = Database::default();
let input = MemDocsInput::new(
&db,
Arc::new(Mutex::new(docs)),
"test/arr_bounds/main.pi".to_string(),
Default::default(),
ActionType::Compile,
None,
None,
);
compile(
&db,
input,
out.to_string(),
Options {
optimization: crate::ast::compiler::HashOptimizationLevel::Less,
genir: true,
printast: false,
flow: false,
fmt: false,
jit: false,
debug: false,
..Default::default()
},
);
let exe = crate::utils::canonicalize(&exe)
.unwrap_or_else(|_| panic!("static compiled file not found {:?}", exe));
eprintln!("exec: {:?}", exe);
let o = Command::new(exe.to_str().unwrap())
.output()
.expect("failed to execute compiled program");
// should trigger index out of bounds, so status should be non-zero
assert!(!o.status.success(), "should trigger index out of bounds");
drop(l);
}

#[cfg(test)]
pub(crate) fn set_test_asset() {
use std::time::SystemTime;
Expand Down
4 changes: 4 additions & 0 deletions src/utils/read_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ pub struct Config {
/// and it's decided by the position of kagari.toml file
#[serde(skip)]
pub root: String,

/// Assert Index Out Of Bounds, default is false
#[serde(default)]
pub assert_index_out_of_bounds: bool,
}

/// ConfigWrapper wraps a config, which represents all configuration of an entry node of a program.
Expand Down
1 change: 1 addition & 0 deletions test/Kagari.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
project = "project1"
entry = "main.pi"

assert_index_out_of_bounds = true

[deps]
project2 = { path = "project2" }
Expand Down
4 changes: 4 additions & 0 deletions test/arr_bounds/Kagari.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
entry = "main.pi"
project = "arr_bounds"

assert_index_out_of_bounds = true
5 changes: 5 additions & 0 deletions test/arr_bounds/main.pi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn main() i64 {
let a = [1];
let d = a[1];
return 0;
}
11 changes: 11 additions & 0 deletions vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@
exit(1);
}

#[is_runtime]
fn pl_index_out_of_bounds(index: i64, len: i64) {
println!(
"index out of bounds occured! index: {}, len: {}",
index, len
);
let bt = Backtrace::new();
println!("{:?}", bt);
exit(1);

Check warning on line 52 in vm/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/lib.rs#L45-L52

Added lines #L45 - L52 were not covered by tests
}

#[is_runtime]
fn __cast_panic() {
println!("invalid cast occured!");
Expand Down
Loading