fix: 修复全局边界安全与内存稳定性问题
This commit is contained in:
@@ -158,6 +158,8 @@ pub(crate) struct CompileCtx {
|
||||
/// Set when alloc_reg() overflows into the reserved this/new.target range (≥254).
|
||||
/// Checked after each emit phase to produce a compile error rather than silent corruption.
|
||||
pub(crate) reg_overflow: bool,
|
||||
pub(crate) const_overflow: bool,
|
||||
pub(crate) jump_overflow: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -222,6 +224,8 @@ impl CompileCtx {
|
||||
after_super_insert: None,
|
||||
after_super_inserted: false,
|
||||
reg_overflow: false,
|
||||
const_overflow: false,
|
||||
jump_overflow: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +284,10 @@ impl CompileCtx {
|
||||
return idx;
|
||||
}
|
||||
|
||||
if self.constants.len() >= u16::MAX as usize {
|
||||
self.const_overflow = true;
|
||||
return u16::MAX;
|
||||
}
|
||||
let idx = self.constants.len() as u16;
|
||||
self.constants.push(c);
|
||||
self.constant_map.insert(key, idx);
|
||||
@@ -287,10 +295,23 @@ impl CompileCtx {
|
||||
}
|
||||
|
||||
let idx = self.constants.len();
|
||||
if idx >= u16::MAX as usize {
|
||||
self.const_overflow = true;
|
||||
return u16::MAX;
|
||||
}
|
||||
self.constants.push(c);
|
||||
idx as u16
|
||||
}
|
||||
|
||||
pub(crate) fn checked_jump_offset(&mut self, offset: isize) -> i16 {
|
||||
if offset < i16::MIN as isize || offset > i16::MAX as isize {
|
||||
self.jump_overflow = true;
|
||||
0
|
||||
} else {
|
||||
offset as i16
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_label(&self, label: Label) -> Result<usize, String> {
|
||||
self.label_map
|
||||
.get(&label)
|
||||
@@ -652,6 +673,12 @@ impl Compiler {
|
||||
if ctx.reg_overflow {
|
||||
return Err("RangeError: function body uses too many registers (max 253)".into());
|
||||
}
|
||||
if ctx.const_overflow {
|
||||
return Err("RangeError: too many constants".into());
|
||||
}
|
||||
if ctx.jump_overflow {
|
||||
return Err("RangeError: jump offset out of range".into());
|
||||
}
|
||||
|
||||
Ok(CompiledModule {
|
||||
bytecode: ctx.bytecode,
|
||||
@@ -717,6 +744,12 @@ impl Compiler {
|
||||
if ctx.reg_overflow {
|
||||
return Err("RangeError: function body uses too many registers (max 253)".into());
|
||||
}
|
||||
if ctx.const_overflow {
|
||||
return Err("RangeError: too many constants".into());
|
||||
}
|
||||
if ctx.jump_overflow {
|
||||
return Err("RangeError: jump offset out of range".into());
|
||||
}
|
||||
|
||||
Ok(CompiledModule {
|
||||
bytecode: ctx.bytecode,
|
||||
@@ -741,3 +774,16 @@ impl Default for Compiler {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::CompileCtx;
|
||||
|
||||
#[test]
|
||||
fn test_jump_offset_overflow_range_error() {
|
||||
let mut ctx = CompileCtx::new();
|
||||
let offset = ctx.checked_jump_offset(i16::MAX as isize + 1);
|
||||
assert_eq!(offset, 0);
|
||||
assert!(ctx.jump_overflow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ impl Compiler {
|
||||
let dup_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, reg, 0));
|
||||
let offset = (short_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, offset));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -189,20 +190,24 @@ impl Compiler {
|
||||
LogicalOperator::And => {
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset));
|
||||
}
|
||||
LogicalOperator::Or => {
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset));
|
||||
}
|
||||
LogicalOperator::Coalesce => {
|
||||
let store_pos = ctx.resolve_label(store_label)?;
|
||||
let offset = (store_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(test_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(test_reg, offset));
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -337,7 +342,8 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, val_reg, default_reg, 0));
|
||||
let after = ctx.bytecode.len();
|
||||
let offset = after as isize - jump_pos as isize;
|
||||
ctx.bytecode[jump_pos] = opcode::encode_jmp_if_false(eq_reg, offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[jump_pos] = opcode::encode_jmp_if_false(eq_reg, offset);
|
||||
Ok(val_reg)
|
||||
}
|
||||
|
||||
@@ -426,9 +432,12 @@ impl Compiler {
|
||||
let tmp_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::INC_PRE, idx_reg, tmp_reg, tmp_reg));
|
||||
let back = loop_start as isize - ctx.bytecode.len() as isize;
|
||||
ctx.emit(opcode::encode_jmp(back as i16));
|
||||
let back = ctx.checked_jump_offset(back);
|
||||
ctx.emit(opcode::encode_jmp(back));
|
||||
let after = ctx.bytecode.len();
|
||||
ctx.bytecode[end_jmp] = opcode::encode_jmp_if_false(has_reg, (after as isize - end_jmp as isize) as i16);
|
||||
let end_offset = after as isize - end_jmp as isize;
|
||||
let end_offset = ctx.checked_jump_offset(end_offset);
|
||||
ctx.bytecode[end_jmp] = opcode::encode_jmp_if_false(has_reg, end_offset);
|
||||
Ok(rest_reg)
|
||||
}
|
||||
|
||||
@@ -1013,7 +1022,8 @@ impl Compiler {
|
||||
|
||||
let else_pos = ctx.resolve_label(else_label)?;
|
||||
let offset = (else_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset));
|
||||
|
||||
let cons_reg = self.emit_statement(&ifs.consequent, ctx)?;
|
||||
let result_reg = ctx.alloc_reg();
|
||||
@@ -1028,7 +1038,8 @@ impl Compiler {
|
||||
if has_alt {
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
}
|
||||
|
||||
if let Some(alt) = &ifs.alternate {
|
||||
@@ -1054,13 +1065,15 @@ impl Compiler {
|
||||
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset));
|
||||
|
||||
self.emit_statement(&wh.body, ctx)?;
|
||||
|
||||
let start_pos = ctx.resolve_label(start_label)?;
|
||||
let offset = (start_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
|
||||
ctx.pop_loop();
|
||||
Ok(None)
|
||||
@@ -1078,7 +1091,8 @@ impl Compiler {
|
||||
|
||||
let start_pos = ctx.resolve_label(start_label)?;
|
||||
let offset = (start_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset));
|
||||
|
||||
ctx.pop_loop();
|
||||
Ok(None)
|
||||
@@ -1112,7 +1126,8 @@ impl Compiler {
|
||||
let test_reg = self.emit_expression(test, ctx)?;
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset));
|
||||
}
|
||||
|
||||
self.emit_statement(&fr.body, ctx)?;
|
||||
@@ -1123,7 +1138,8 @@ impl Compiler {
|
||||
|
||||
let start_pos = ctx.resolve_label(start_label)?;
|
||||
let offset = (start_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
|
||||
ctx.pop_loop();
|
||||
|
||||
@@ -1145,7 +1161,8 @@ impl Compiler {
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let cleanup_jmp_offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_false(done_reg, 2));
|
||||
ctx.emit(opcode::encode_jmp(cleanup_jmp_offset as i16));
|
||||
let cleanup_jmp_offset = ctx.checked_jump_offset(cleanup_jmp_offset);
|
||||
ctx.emit(opcode::encode_jmp(cleanup_jmp_offset));
|
||||
|
||||
let key_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::FOR_IN_NEXT, key_reg, 0, 0));
|
||||
@@ -1175,7 +1192,8 @@ impl Compiler {
|
||||
|
||||
let start_pos = ctx.resolve_label(start_label)?;
|
||||
let offset = (start_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
|
||||
ctx.emit(opcode::encode(OpCode::FOR_IN_CLEANUP, 0, 0, 0));
|
||||
|
||||
@@ -1195,7 +1213,8 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::FOR_OF_DONE, has_reg, 0, 0));
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_false(has_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_false(has_reg, offset));
|
||||
|
||||
let val_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::FOR_OF_NEXT, val_reg, 0, 0));
|
||||
@@ -1222,7 +1241,8 @@ impl Compiler {
|
||||
self.emit_statement(&fo.body, ctx)?;
|
||||
let start_pos = ctx.resolve_label(start_label)?;
|
||||
let offset = (start_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
ctx.emit(opcode::encode(OpCode::FOR_OF_CLOSE, 0, 0, 0));
|
||||
ctx.pop_loop();
|
||||
Ok(None)
|
||||
@@ -1245,7 +1265,8 @@ impl Compiler {
|
||||
let case_label = Label::SwitchCase(id, case_idx as u32);
|
||||
let body_pos = ctx.resolve_label(case_label)?;
|
||||
let offset = (body_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_true(eq_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_true(eq_reg, offset));
|
||||
ctx.restore_reg_checkpoint(compare_reg_checkpoint);
|
||||
}
|
||||
}
|
||||
@@ -1254,7 +1275,8 @@ impl Compiler {
|
||||
if !has_default {
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
}
|
||||
|
||||
for case in cases.iter() {
|
||||
@@ -1275,14 +1297,16 @@ impl Compiler {
|
||||
};
|
||||
let break_pos = ctx.resolve_label(break_label)?;
|
||||
let offset = (break_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
Ok(None)
|
||||
}
|
||||
Statement::ContinueStatement(_) => {
|
||||
let (_, continue_label) = ctx.current_loop().ok_or("continue outside loop".to_string())?;
|
||||
let continue_pos = ctx.resolve_label(*continue_label)?;
|
||||
let offset = (continue_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
Ok(None)
|
||||
}
|
||||
Statement::FunctionDeclaration(fd) => {
|
||||
@@ -1395,7 +1419,8 @@ impl Compiler {
|
||||
|
||||
if let Some(try_begin_pc) = try_begin_pos {
|
||||
let offset = catch_label_pc as isize - (try_begin_pc as isize);
|
||||
ctx.bytecode[try_begin_pc] = opcode::encode_try_begin(offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[try_begin_pc] = opcode::encode_try_begin(offset);
|
||||
}
|
||||
|
||||
if let Some(catch) = &ts.handler {
|
||||
@@ -1423,12 +1448,14 @@ impl Compiler {
|
||||
ctx.label_map.insert(finally_label, finally_label_pc);
|
||||
if let Some(fb_pos) = try_finally_begin_pos {
|
||||
let offset = finally_label_pc as isize - (fb_pos as isize);
|
||||
ctx.bytecode[fb_pos] = opcode::encode_try_finally_begin(offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[fb_pos] = opcode::encode_try_finally_begin(offset);
|
||||
}
|
||||
|
||||
if let Some(jmp_pos) = jmp_skip_pos {
|
||||
let offset = finally_label_pc as isize - (jmp_pos as isize);
|
||||
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset);
|
||||
}
|
||||
|
||||
let mut last_finally_result: Option<u8> = None;
|
||||
@@ -1446,7 +1473,8 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::TRY_FINALLY_END, 0, 0, 0));
|
||||
} else if let Some(jmp_pos) = jmp_skip_pos {
|
||||
let offset = ctx.bytecode.len() as isize - (jmp_pos as isize);
|
||||
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset);
|
||||
}
|
||||
|
||||
let try_end_pc = ctx.bytecode.len();
|
||||
@@ -1589,13 +1617,15 @@ impl Compiler {
|
||||
let offset = (short_pos as isize) - (jump_pos as isize);
|
||||
let instr = ctx.bytecode[jump_pos];
|
||||
let rd = opcode::rd(instr);
|
||||
ctx.bytecode[jump_pos] = opcode::encode_jmp_if_nullish(rd, offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[jump_pos] = opcode::encode_jmp_if_nullish(rd, offset);
|
||||
}
|
||||
let true_idx = ctx.add_constant(Constant::Boolean(true));
|
||||
ctx.emit_load_const(result_reg, true_idx);
|
||||
let end_pos = ctx.bytecode.len();
|
||||
let offset = (end_pos as isize) - (end_jump_pos as isize);
|
||||
ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset);
|
||||
Ok(result_reg)
|
||||
}
|
||||
_ => Err("invalid delete target".into()),
|
||||
@@ -1640,14 +1670,16 @@ impl Compiler {
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
|
||||
let offset = (else_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset));
|
||||
|
||||
let cons_reg = self.emit_expression(&cond.consequent, ctx)?;
|
||||
let result_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, cons_reg, 0));
|
||||
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
|
||||
let alt_reg = self.emit_expression(&cond.alternate, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, alt_reg, 0));
|
||||
@@ -1682,9 +1714,11 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0));
|
||||
let end_pos = ctx.bytecode.len();
|
||||
let offset = (rhs_pos as isize) - (nullish_jump_pos as isize);
|
||||
ctx.bytecode[nullish_jump_pos] = opcode::encode_jmp_if_nullish(dup_reg, offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[nullish_jump_pos] = opcode::encode_jmp_if_nullish(dup_reg, offset);
|
||||
let offset = (end_pos as isize) - (end_jump_pos as isize);
|
||||
ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset as i16);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset);
|
||||
return Ok(dup_reg);
|
||||
}
|
||||
let skip_label = match log.operator {
|
||||
@@ -1700,10 +1734,12 @@ impl Compiler {
|
||||
let offset = (skip_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
match log.operator {
|
||||
LogicalOperator::And => {
|
||||
ctx.emit(opcode::encode_jmp_if_false(dup_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_false(dup_reg, offset));
|
||||
}
|
||||
LogicalOperator::Or => {
|
||||
ctx.emit(opcode::encode_jmp_if_true(dup_reg, offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_true(dup_reg, offset));
|
||||
}
|
||||
LogicalOperator::Coalesce => unreachable!(),
|
||||
}
|
||||
@@ -1768,7 +1804,8 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, value_reg, 0));
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
let undefined_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.emit_load_const(result_reg, undefined_idx);
|
||||
Ok(result_reg)
|
||||
|
||||
Reference in New Issue
Block a user