diff --git a/Cargo.lock b/Cargo.lock index 548b157..c6dd6bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,7 @@ dependencies = [ "lang-c 0.8.0 (git+https://github.com/kaist-cp/lang-c)", "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -367,6 +368,14 @@ name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -443,6 +452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index ee1bb4f..a357e3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,4 @@ failure = "0.1.7" tempfile = "3.1.0" ordered-float = "1.0" hexf = "0.1.0" +wait-timeout = "0.2.0" diff --git a/README.md b/README.md index 88e09e3..dc82db5 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ built by the test script. For more information, we refer to the ### Reduce When the fuzzer finds a buggy input program for your compiler, it is highly likely that the input -program is too big to manually inspect. We use `creduce` that reduces the buggy input program as +program is too big to manually inspect. We use `creduce` that reduces the buggy input program as much as possible. Suppose `tests/test_polished.c` is the buggy input program. Then the following script reduces the @@ -96,3 +96,6 @@ The script performs unguided test-case reduction using `creduce`: given a buggy reduces the program; check if the reduced program still fails on the test, and if so, replaces the given program with the reduced one; repeat until you get a small enough buggy program. For more information, we refer to the [Creduce](https://embed.cs.utah.edu/creduce/) homepage. + +**[NOTICE]** The fuzzer supports Ubuntu 18.04 only. It may work for other platforms, but if it +doesn't, please run the fuzzer in Ubuntu 18.04. diff --git a/examples/array4.c b/examples/array4.c new file mode 100644 index 0000000..a374bc5 --- /dev/null +++ b/examples/array4.c @@ -0,0 +1,10 @@ +int main() { + int a[10]; + int *p = a; + + for (int i = 0; i < 10; i++) { + *(p++) = i; + } + + return a[5] == 5; +} diff --git a/examples/bitwise.c b/examples/bitwise.c new file mode 100644 index 0000000..abc7bb6 --- /dev/null +++ b/examples/bitwise.c @@ -0,0 +1,13 @@ +int main() { + unsigned char a = -1; + unsigned char b = -128; + unsigned char c = 127; + unsigned char d = b | a; // -1 (255) + unsigned char e = b & a; // -128 (128) + unsigned char f = b & c; // 0 (0) + unsigned char g = b | c; // -1 (255) + unsigned char h = -1 ^ -1; // 0 (0) + unsigned char i = -1 ^ 0; // -1 (255) + + return d == 255 && e == 128 && f == 0 && g == 255 && h == 0 && i == 255; +} diff --git a/examples/integer_literal.c b/examples/integer_literal.c new file mode 100644 index 0000000..5858302 --- /dev/null +++ b/examples/integer_literal.c @@ -0,0 +1,5 @@ +int main() { + short temp = 0; + unsigned int temp2 = 4294967163; + return (char)(temp ^ temp2) == 123; +} diff --git a/examples/integer_literal2.c b/examples/integer_literal2.c new file mode 100644 index 0000000..9bfabf0 --- /dev/null +++ b/examples/integer_literal2.c @@ -0,0 +1,5 @@ +int main() { + int temp = 0; + // `0xFFFFFFFF` is translated as `unsigned int` not `int` + return temp < 0xFFFFFFFF; +} diff --git a/examples/logical_op.c b/examples/logical_op.c new file mode 100644 index 0000000..aeb9bc7 --- /dev/null +++ b/examples/logical_op.c @@ -0,0 +1,16 @@ +int main() { + int a = 0; + int b = 0; + int c = 0; + int d = 0; + + if ((a = 1) || (b = 1)) { + b++; + } + + if ((c = 1) && (d = 1)) { + d++; + } + + return b == 1 && d == 2; +} diff --git a/examples/minus_constant.c b/examples/minus_constant.c new file mode 100644 index 0000000..948d006 --- /dev/null +++ b/examples/minus_constant.c @@ -0,0 +1,8 @@ +int a = -1; +long b = -1l; +float c = -1.5f; +double d = -1.5; + +int main() { + return (a + b + (int)c + (long)d) == -4; +} diff --git a/examples/shift.c b/examples/shift.c new file mode 100644 index 0000000..fc99bad --- /dev/null +++ b/examples/shift.c @@ -0,0 +1,7 @@ +int main() { + char a = -1; + char b = a << 1; + unsigned char c = (unsigned char)b >> 1; + + return b == -2 && c == 0x7F; +} diff --git a/examples/test.c b/examples/test.c index 896a578..2262d20 100644 --- a/examples/test.c +++ b/examples/test.c @@ -1,10 +1,11 @@ int main() { long int l = 1; long l2 = 2; - short int s = 3; - short s2 = 4; - int i = 5; - char c = 6; + long long l3 = 3; + short int s = 4; + short s2 = 5; + int i = 6; + char c = 7; - return l + l2 + s + s2 + i + c; + return (l + l2 + l3 + s + s2 + i + c) == 28; } diff --git a/examples/typecast.c b/examples/typecast.c new file mode 100644 index 0000000..7ee0b79 --- /dev/null +++ b/examples/typecast.c @@ -0,0 +1,5 @@ +char temp = 0x00L; + +int main(){ + return (temp = 0xEF36L) >= (2L); +} diff --git a/examples/unary.c b/examples/unary.c new file mode 100644 index 0000000..81c55d4 --- /dev/null +++ b/examples/unary.c @@ -0,0 +1,4 @@ +int main() { + unsigned char temp = 0x00L; + return 1 > (--temp); +} diff --git a/src/ir/dtype.rs b/src/ir/dtype.rs index b395709..97640b1 100644 --- a/src/ir/dtype.rs +++ b/src/ir/dtype.rs @@ -23,7 +23,7 @@ pub trait HasDtype { #[derive(Default)] struct BaseDtype { scalar: Option, - size_modifier: Option, + size_modifiers: Vec, signed_option: Option, typedef_name: Option, is_const: bool, @@ -129,12 +129,7 @@ impl BaseDtype { self.scalar = Some(type_specifier.clone()); } ast::TypeSpecifier::Short | ast::TypeSpecifier::Long => { - if self.size_modifier.is_some() { - return Err(DtypeError::Misc { - message: "two or more size modifiers in declaration specifiers".to_string(), - }); - } - self.size_modifier = Some(type_specifier.clone()); + self.size_modifiers.push(type_specifier.clone()) } ast::TypeSpecifier::TypedefName(identifier) => { if self.typedef_name.is_some() { @@ -277,7 +272,7 @@ impl TryFrom for Dtype { fn try_from(spec: BaseDtype) -> Result { assert!( !(spec.scalar.is_none() - && spec.size_modifier.is_none() + && spec.size_modifiers.is_empty() && spec.signed_option.is_none() && spec.typedef_name.is_none() && !spec.is_const), @@ -303,32 +298,40 @@ impl TryFrom for Dtype { ast::TypeSpecifier::Int => Self::INT, ast::TypeSpecifier::Float => Self::FLOAT, ast::TypeSpecifier::Double => Self::DOUBLE, - ast::TypeSpecifier::Unsigned | ast::TypeSpecifier::Signed => { - panic!("Signed option to scalar is not supported") - } _ => panic!("Dtype::try_from::: {:?} is not a scalar type", t), } } else { Self::default() }; - // Applies size modifier - if let Some(size_modifier) = spec.size_modifier { - if dtype != Self::INT { - return Err(DtypeError::Misc { - message: "size modifier can only be used with `int`".to_string(), - }); - } - - dtype = match size_modifier { + let number_of_modifier = spec.size_modifiers.len(); + dtype = match number_of_modifier { + 0 => dtype, + 1 => match spec.size_modifiers[0] { ast::TypeSpecifier::Short => Self::SHORT, ast::TypeSpecifier::Long => Self::LONG, _ => panic!( - "Dtype::try_from::: {:?} is not a size modifier", - size_modifier + "Dtype::try_from::: {:?} is not a size modifiers", + spec.size_modifiers ), + }, + 2 => { + if spec.size_modifiers[0] != ast::TypeSpecifier::Long + || spec.size_modifiers[1] != ast::TypeSpecifier::Long + { + return Err(DtypeError::Misc { + message: "two or more size modifiers in declaration specifiers" + .to_string(), + }); + } + Self::LONGLONG } - } + _ => { + return Err(DtypeError::Misc { + message: "two or more size modifiers in declaration specifiers".to_string(), + }) + } + }; // Applies signedness. if let Some(signed_option) = spec.signed_option { @@ -693,8 +696,7 @@ impl Dtype { let declarator_kind = &declarator.kind; match &declarator_kind.node { - ast::DeclaratorKind::Abstract => panic!("ast::DeclaratorKind::Abstract is unsupported"), - ast::DeclaratorKind::Identifier(_) => Ok(self), + ast::DeclaratorKind::Abstract | ast::DeclaratorKind::Identifier(_) => Ok(self), ast::DeclaratorKind::Declarator(declarator) => { self.with_ast_declarator(&declarator.node) } diff --git a/src/ir/interp.rs b/src/ir/interp.rs index 06d7311..0b3dbd2 100644 --- a/src/ir/interp.rs +++ b/src/ir/interp.rs @@ -89,6 +89,14 @@ impl Value { Self::Pointer { bid, offset, dtype } } + #[inline] + fn array(inner_dtype: Dtype, values: Vec) -> Self { + Self::Array { + inner_dtype, + values, + } + } + #[inline] fn get_int(self) -> Option<(u128, usize, bool)> { if let Value::Int { @@ -130,7 +138,12 @@ impl Value { } => Self::int(u128::default(), *width, *is_signed), ir::Dtype::Float { width, .. } => Self::float(f64::default(), *width), ir::Dtype::Pointer { inner, .. } => Self::nullptr(inner.deref().clone()), - ir::Dtype::Array { .. } => panic!("array type does not have a default value"), + ir::Dtype::Array { inner, size } => { + let values = (0..*size) + .map(|_| Self::default_from_dtype(inner)) + .collect(); + Self::array(inner.deref().clone(), values) + } ir::Dtype::Function { .. } => panic!("function type does not have a default value"), ir::Dtype::Typedef { .. } => panic!("typedef should be replaced by real dtype"), } @@ -251,6 +264,7 @@ impl<'i> StackFrame<'i> { mod calculator { use super::Value; + use crate::ir::*; use lang_c::ast; // TODO: change to template function in the future @@ -278,53 +292,126 @@ mod calculator { assert_eq!(lhs_w, rhs_w); assert_eq!(lhs_s, rhs_s); - match op { - // TODO: consider signed value in the future - ast::BinaryOperator::Plus => Ok(Value::int(lhs + rhs, lhs_w, lhs_s)), - ast::BinaryOperator::Minus => Ok(Value::int(lhs - rhs, lhs_w, lhs_s)), - ast::BinaryOperator::Multiply => Ok(Value::int(lhs * rhs, lhs_w, lhs_s)), - ast::BinaryOperator::Modulo => Ok(Value::int(lhs % rhs, lhs_w, lhs_s)), + let result = match op { + // TODO: explain why plus & minus do not need to consider `is_signed' + ast::BinaryOperator::Plus => (lhs as i128 + rhs as i128) as u128, + ast::BinaryOperator::Minus => (lhs as i128 - rhs as i128) as u128, + ast::BinaryOperator::Multiply => { + if lhs_s { + (lhs as i128 * rhs as i128) as u128 + } else { + lhs * rhs + } + } + ast::BinaryOperator::Divide => { + if lhs_s { + (lhs as i128 / rhs as i128) as u128 + } else { + lhs / rhs + } + } + ast::BinaryOperator::Modulo => { + if lhs_s { + (lhs as i128 % rhs as i128) as u128 + } else { + lhs % rhs + } + } + ast::BinaryOperator::ShiftLeft => { + let rhs = if lhs_s { + let rhs = rhs as i128; + assert!(rhs >= 0); + assert!(rhs < (lhs_w as i128)); + rhs as u128 + } else { + assert!(rhs < (lhs_w as u128)); + rhs + }; + + lhs << rhs + } + ast::BinaryOperator::ShiftRight => { + if lhs_s { + // arithmetic shift right + let rhs = rhs as i128; + assert!(rhs >= 0); + assert!(rhs < (lhs_w as i128)); + ((lhs as i128) >> rhs) as u128 + } else { + // logical shift right + assert!(rhs < (lhs_w as u128)); + let bit_mask = (1u128 << lhs_w as u128) - 1; + let lhs = lhs & bit_mask; + lhs >> rhs + } + } + ast::BinaryOperator::BitwiseAnd => lhs & rhs, + ast::BinaryOperator::BitwiseXor => lhs ^ rhs, + ast::BinaryOperator::BitwiseOr => lhs | rhs, ast::BinaryOperator::Equals => { let result = if lhs == rhs { 1 } else { 0 }; - Ok(Value::int(result, 1, false)) + return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::NotEquals => { let result = if lhs != rhs { 1 } else { 0 }; - Ok(Value::int(result, 1, false)) + return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::Less => { - // TODO: consider signed option - let result = if lhs < rhs { 1 } else { 0 }; - Ok(Value::int(result, 1, false)) + let condition = if lhs_s { + (lhs as i128) < (rhs as i128) + } else { + lhs < rhs + }; + let result = if condition { 1 } else { 0 }; + return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::Greater => { - // TODO: consider signed option - let result = if lhs > rhs { 1 } else { 0 }; - Ok(Value::int(result, 1, false)) + let condition = if lhs_s { + (lhs as i128) > (rhs as i128) + } else { + lhs > rhs + }; + let result = if condition { 1 } else { 0 }; + return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::LessOrEqual => { - // TODO: consider signed option - let result = if lhs <= rhs { 1 } else { 0 }; - Ok(Value::int(result, 1, false)) + let condition = if lhs_s { + (lhs as i128) <= (rhs as i128) + } else { + lhs <= rhs + }; + let result = if condition { 1 } else { 0 }; + return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::GreaterOrEqual => { - // TODO: consider signed option - let result = if lhs >= rhs { 1 } else { 0 }; - Ok(Value::int(result, 1, false)) - } - ast::BinaryOperator::LogicalAnd => { - assert!(lhs < 2); - assert!(rhs < 2); - let result = lhs | rhs; - Ok(Value::int(result, 1, lhs_s)) + let condition = if lhs_s { + (lhs as i128) >= (rhs as i128) + } else { + lhs >= rhs + }; + let result = if condition { 1 } else { 0 }; + return Ok(Value::int(result, 1, false)); } _ => todo!( "calculate_binary_operator_expression: not supported operator {:?}", op ), - } + }; + + let result = if lhs_s { + sign_extension(result, lhs_w as u128) + } else { + trim_unnecessary_bits(result, lhs_w as u128) + }; + + Ok(Value::int(result, lhs_w, lhs_s)) } - _ => todo!(), + (op, lhs, rhs) => todo!( + "calculate_binary_operator_expression: not supported case for {:?} {:?} {:?}", + op, + lhs, + rhs + ), } } @@ -350,8 +437,14 @@ mod calculator { is_signed, }, ) => { - assert!(is_signed); - let result = -(value as i128); + // TODO: check what is exact behavior of appling minus to unsigned value + let result = if is_signed { + (-(value as i128)) as u128 + } else { + let result = (-(value as i128)) as u128; + let bit_mask = (1u128 << (width as u128)) - 1; + result & bit_mask + }; Ok(Value::int(result as u128, width, is_signed)) } ( @@ -367,27 +460,95 @@ mod calculator { let result = if value == 0 { 1 } else { 0 }; Ok(Value::int(result, width, is_signed)) } - _ => todo!(), + (op, operand) => todo!( + "calculate_unary_operator_expression: not supported case for {:?} {:?}", + op, + operand, + ), } } - pub fn calculate_typecast(value: Value, dtype: crate::ir::Dtype) -> Result { + pub fn calculate_typecast(value: Value, dtype: Dtype) -> Result { + if value.dtype() == dtype { + return Ok(value); + } + match (value, dtype) { (Value::Undef { .. }, _) => Err(()), // TODO: distinguish zero/signed extension in the future // TODO: consider truncate in the future ( - Value::Int { value, .. }, - crate::ir::Dtype::Int { + Value::Int { value, width, .. }, + Dtype::Int { + width: target_width, + is_signed: target_signed, + .. + }, + ) => { + // Other cases + let result = if target_signed { + if width >= target_width { + // TODO: explain the logic in the future + let value = trim_unnecessary_bits(value, target_width as u128); + sign_extension(value, target_width as u128) + } else { + value + } + } else { + trim_unnecessary_bits(value, target_width as u128) + }; + + Ok(Value::int(result, target_width, target_signed)) + } + ( + Value::Int { + value, is_signed, .. + }, + Dtype::Float { width, .. }, + ) => { + let casted_value = if is_signed { + value as i128 as f64 + } else { + value as f64 + }; + Ok(Value::float(casted_value, width)) + } + ( + Value::Float { value, .. }, + Dtype::Int { width, is_signed, .. }, - ) => Ok(Value::int(value, width, is_signed)), - (Value::Float { value, .. }, crate::ir::Dtype::Float { width, .. }) => { + ) => { + let casted_value = if is_signed { + value as i128 as u128 + } else { + value as u128 + }; + Ok(Value::int(casted_value, width, is_signed)) + } + (Value::Float { value, .. }, Dtype::Float { width, .. }) => { Ok(Value::float(value, width)) } - (value, dtype) => todo!("calculate_typecast ({:?}) {:?}", dtype, value), + (value, dtype) => todo!("calculate_typecast ({:?}) {:?}", value, dtype), } } + + #[inline] + fn sign_extension(value: u128, width: u128) -> u128 { + let base = 1u128 << (width - 1); + if value >= base { + let bit_mask = -1i128 << (width as i128); + value | bit_mask as u128 + } else { + value + } + } + + #[inline] + fn trim_unnecessary_bits(value: u128, width: u128) -> u128 { + let bit_mask = (1u128 << width) - 1; + value & bit_mask + } } // TODO @@ -397,7 +558,7 @@ enum Byte { Undef, Concrete(u8), Pointer { - bid: usize, + bid: Option, offset: isize, index: usize, }, @@ -415,7 +576,7 @@ impl Byte { } #[inline] - fn pointer(bid: usize, offset: isize, index: usize) -> Self { + fn pointer(bid: Option, offset: isize, index: usize) -> Self { Self::Pointer { bid, offset, index } } @@ -427,7 +588,7 @@ impl Byte { } } - fn get_pointer(&self) -> Option<(usize, isize, usize)> { + fn get_pointer(&self) -> Option<(Option, isize, usize)> { if let Self::Pointer { bid, offset, index } = self { Some((*bid, *offset, *index)) } else { @@ -482,6 +643,7 @@ impl Byte { Ok(Value::int(value, *width, *is_signed)) } ir::Dtype::Float { width, .. } => { + let size = (*width + Dtype::BITS_OF_BYTE - 1) / Dtype::BITS_OF_BYTE; let value = some_or!( bytes .by_ref() @@ -491,7 +653,7 @@ impl Byte { return Ok(Value::undef(dtype.clone())) ); let value = Self::bytes_to_u128(&value, false); - let value = if *width == Dtype::SIZE_OF_FLOAT { + let value = if size == Dtype::SIZE_OF_FLOAT { f32::from_bits(value as u32) as f64 } else { f64::from_bits(value as u64) @@ -519,7 +681,7 @@ impl Byte { { Value::undef(inner.deref().clone()) } else { - Value::pointer(Some(*bid), *offset, inner.deref().clone()) + Value::pointer(*bid, *offset, inner.deref().clone()) }, ) } @@ -556,19 +718,19 @@ impl Byte { } Value::Float { value, width } => { let size = (*width + Dtype::BITS_OF_BYTE - 1) / Dtype::BITS_OF_BYTE; - let value: u128 = match size { + let value_bits: u128 = match size { Dtype::SIZE_OF_FLOAT => (*value as f32).to_bits() as u128, Dtype::SIZE_OF_DOUBLE => (*value as f64).to_bits() as u128, _ => panic!("value_to_bytes: {} is not a valid float size", size), }; - Self::u128_to_bytes(value, size) + Self::u128_to_bytes(value_bits, size) .iter() .map(|b| Self::concrete(*b)) .collect::>() } Value::Pointer { bid, offset, .. } => (0..Dtype::SIZE_OF_POINTER) - .map(|i| Self::pointer(bid.unwrap(), *offset, i)) + .map(|i| Self::pointer(*bid, *offset, i)) .collect(), Value::Array { inner_dtype, @@ -698,9 +860,29 @@ impl<'i> State<'i> { Value::default_from_dtype(&dtype) }; + // Type cast + let value = + calculator::calculate_typecast(value, dtype.clone()).map_err(|_| { + InterpreterError::Misc { + func_name: self.stack_frame.func_name.clone(), + pc: self.stack_frame.pc, + msg: "calculate_typecast when initialize global variable" + .into(), + } + })?; + + self.memory.store(bid, 0, &value); + } + ir::Dtype::Array { .. } => { + let value = if let Some(_list_init) = initializer { + // TODO: is type cast required? + todo!("Initializer::List is needed") + } else { + Value::default_from_dtype(&dtype) + }; + self.memory.store(bid, 0, &value); } - ir::Dtype::Array { .. } => todo!("Initializer::List is needed"), ir::Dtype::Function { .. } => panic!("function variable does not exist"), ir::Dtype::Typedef { .. } => panic!("typedef should be replaced by real dtype"), }, diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 64729d6..486ae93 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -496,28 +496,30 @@ impl TryFrom<&ast::Constant> for Constant { fn try_from(constant: &ast::Constant) -> Result { match constant { ast::Constant::Integer(integer) => { - let is_signed = !integer.suffix.unsigned; - let dtype = match integer.suffix.size { ast::IntegerSize::Int => Dtype::INT, ast::IntegerSize::Long => Dtype::LONG, ast::IntegerSize::LongLong => Dtype::LONGLONG, - } - .set_signed(is_signed); + }; let pat = match integer.base { ast::IntegerBase::Decimal => Self::DECIMAL, ast::IntegerBase::Octal => Self::OCTAL, ast::IntegerBase::Hexadecimal => Self::HEXADECIMAL, }; + let value = u128::from_str_radix(integer.number.deref(), pat).unwrap(); - let value = if is_signed { - i128::from_str_radix(integer.number.deref(), pat).unwrap() as u128 - } else { - u128::from_str_radix(integer.number.deref(), pat).unwrap() + let is_signed = !integer.suffix.unsigned && { + // Even if `suffix` represents `signed`, integer literal cannot be translated + // to minus value. For this reason, if the sign bit is on, dtype automatically + // transformed to `unsigned`. Let's say integer literal is `0xFFFFFFFF`, + // it translated to unsigned integer even though it has no `U` suffix. + let width = dtype.get_int_width().unwrap(); + let threshold = 1u128 << (width as u128 - 1); + value < threshold }; - Ok(Self::int(value, dtype)) + Ok(Self::int(value, dtype.set_signed(is_signed))) } ast::Constant::Float(float) => { let pat = match float.base { @@ -529,18 +531,34 @@ impl TryFrom<&ast::Constant> for Constant { ast::FloatFormat::Float => { // Casting from an f32 to an f64 is perfect and lossless (f32 -> f64) // https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#type-cast-expressions - let value = if pat == Self::DECIMAL { - float.number.parse::().unwrap() as f64 - } else { - parse_hexf32(float.number.deref(), false).unwrap() as f64 + let value = match pat { + Self::DECIMAL => float.number.parse::().unwrap() as f64, + Self::HEXADECIMAL => { + let mut hex_number = "0x".to_string(); + hex_number.push_str(float.number.deref()); + parse_hexf32(&hex_number, true).unwrap() as f64 + } + _ => panic!( + "Constant::try_from::<&ast::Constant>: \ + {:?} is not a pattern of `pat`", + pat + ), }; (Dtype::FLOAT, value) } ast::FloatFormat::Double => { - let value = if pat == Self::DECIMAL { - float.number.parse::().unwrap() - } else { - parse_hexf64(float.number.deref(), false).unwrap() + let value = match pat { + Self::DECIMAL => float.number.parse::().unwrap(), + Self::HEXADECIMAL => { + let mut hex_number = "0x".to_string(); + hex_number.push_str(float.number.deref()); + parse_hexf64(&hex_number, true).unwrap() + } + _ => panic!( + "Constant::try_from::<&ast::Constant>: \ + {:?} is not a pattern of `pat`", + pat + ), }; (Dtype::DOUBLE, value) } @@ -568,10 +586,23 @@ impl TryFrom<&ast::Expression> for Constant { type Error = (); fn try_from(expr: &ast::Expression) -> Result { - if let ast::Expression::Constant(constant) = expr { - Self::try_from(&constant.node) - } else { - Err(()) + match expr { + ast::Expression::Constant(constant) => Self::try_from(&constant.node), + ast::Expression::UnaryOperator(unary) => { + let constant = Self::try_from(&unary.node.operand.node)?; + // When an IR is generated, there are cases where some expressions must be + // interpreted unconditionally as compile-time constant value. In this case, + // we need to translate also the expression applied `minus` unary operator + // to compile-time constant value directly. + // Let's say expression is `case -1: { .. }`, + // `-1` must be interpreted to compile-time constant value. + match &unary.node.operator.node { + ast::UnaryOperator::Minus => Ok(constant.minus()), + ast::UnaryOperator::Plus => Ok(constant), + _ => Err(()), + } + } + _ => Err(()), } } } @@ -655,6 +686,33 @@ impl Constant { } } + #[inline] + fn minus(self) -> Self { + match self { + Self::Int { + value, + width, + is_signed, + } => { + assert!(is_signed); + let minus_value = -(value as i128); + Self::Int { + value: minus_value as u128, + width, + is_signed, + } + } + Self::Float { mut value, width } => { + *value.as_mut() *= -1.0f64; + Self::Float { value, width } + } + _ => panic!( + "constant value generated by `Constant::from_ast_expression` \ + must be `Constant{{Int, Float}}`" + ), + } + } + pub fn is_undef(&self) -> bool { if let Self::Undef { .. } = self { true @@ -669,7 +727,17 @@ impl fmt::Display for Constant { match self { Self::Undef { .. } => write!(f, "undef"), Self::Unit => write!(f, "unit"), - Self::Int { value, .. } => write!(f, "{}", value), + Self::Int { + value, is_signed, .. + } => write!( + f, + "{}", + if *is_signed { + (*value as i128).to_string() + } else { + value.to_string() + } + ), Self::Float { value, .. } => write!(f, "{}", value), Self::GlobalVariable { name, .. } => write!(f, "%{}", name), } diff --git a/src/ir/write_ir.rs b/src/ir/write_ir.rs index 71238e3..400b16f 100644 --- a/src/ir/write_ir.rs +++ b/src/ir/write_ir.rs @@ -37,8 +37,18 @@ impl WriteLine for (&String, &Declaration) { let decl = self.1; match decl { - Declaration::Variable { dtype, .. } => { - writeln!(write, "{} = {}", name, dtype)?; + Declaration::Variable { dtype, initializer } => { + writeln!( + write, + "{} = {} {}", + name, + if let Some(init) = initializer { + init.to_string() + } else { + "default".to_string() + }, + dtype + )?; } Declaration::Function { signature, @@ -189,19 +199,28 @@ impl WriteString for Operand { impl WriteOp for ast::BinaryOperator { fn write_operation(&self) -> String { + // TODO: represent signed & unsigned if necessary match self { Self::Multiply => "mul", Self::Divide => "div", Self::Modulo => "mod", Self::Plus => "add", Self::Minus => "sub", + Self::ShiftLeft => "shl", + Self::ShiftRight => "shr", Self::Equals => "cmp eq", Self::NotEquals => "cmp ne", Self::Less => "cmp lt", Self::LessOrEqual => "cmp le", Self::Greater => "cmp gt", Self::GreaterOrEqual => "cmp ge", - _ => todo!(), + Self::BitwiseAnd => "and", + Self::BitwiseXor => "xor", + Self::BitwiseOr => "or", + _ => todo!( + "ast::BinaryOperator::WriteOp: write operation for {:?} is needed", + self + ), } .to_string() } @@ -210,8 +229,13 @@ impl WriteOp for ast::BinaryOperator { impl WriteOp for ast::UnaryOperator { fn write_operation(&self) -> String { match self { + Self::Plus => "plus", Self::Minus => "minus", - _ => todo!(), + Self::Negate => "negate", + _ => todo!( + "ast::UnaryOperator::WriteOp: write operation for {:?} is needed", + self + ), } .to_string() } diff --git a/src/tests.rs b/src/tests.rs index ddb6cd6..06dff1b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -3,6 +3,7 @@ use std::fs::{self, File}; use std::path::Path; use std::process::{Command, Stdio}; use tempfile::tempdir; +use wait_timeout::ChildExt; use crate::*; @@ -45,18 +46,28 @@ pub fn test_irgen(unit: &TranslationUnit, path: &Path) { } // Execute compiled executable - let status = Command::new(fs::canonicalize(bin_path.clone()).unwrap()) - .status() - .expect("failed to execute the compiled executable") - .code() - .expect("failed to return an exit code"); + let mut child = Command::new(fs::canonicalize(bin_path.clone()).unwrap()) + .spawn() + .expect("failed to execute the compiled executable"); - // Remove compiled executable Command::new("rm") .arg(bin_path) .status() .expect("failed to remove compiled executable"); + let status = some_or!( + child + .wait_timeout_ms(500) + .expect("failed to obtain exit status from child process"), + { + println!("timeout occurs"); + child.kill().unwrap(); + child.wait().unwrap(); + return; + } + ); + let status = some_or!(status.code(), return); + let ir = match Irgen::default().translate(unit) { Ok(ir) => ir, Err(irgen_error) => panic!("{}", irgen_error), @@ -73,5 +84,6 @@ pub fn test_irgen(unit: &TranslationUnit, path: &Path) { panic!("non-integer value occurs") }; + println!("kecc: {:?}\ngcc: {:?}", result, status); assert_eq!(result, ir::Value::int(status as u128, 32, true)); } diff --git a/tests/fuzz.py b/tests/fuzz.py index 86a05e3..7c7f27d 100644 --- a/tests/fuzz.py +++ b/tests/fuzz.py @@ -23,7 +23,9 @@ REPLACE_DICT = { "long __undefined;": "", "return 0;": "return crc32_context % 128;", r"__attribute__\s*\(\(.*\)\)": "", - "_Float128": "long double", + "_Float128": "double", + "long double": "double", + "(\+[0-9^FL]*)L": r"\1", "union": "struct", r"enum[\w\s]*\{[^\}]*\};": "", r"typedef enum[\w\s]*\{[^;]*;[\s_A-Z]*;": "", @@ -38,21 +40,23 @@ REPLACE_DICT = { r"[^\n]*_IO_2_1_[^;]*;": "", # extern을 지우면서 생긴 size를 알 수 없는 struct 삭제 r"__asm\s*\([^\)]*\)": "", # asm extension in mac r"__asm__\s*\([^\)]*\)": "", # asm extension in linux + "typedef __builtin_va_list __gnuc_va_list;": "", + "typedef __gnuc_va_list va_list;": "", # To check fuzzer before make kecc support struct type - "struct[^}]*};": "", - " struct[^{]*[^}]*}[^;]*;": "", - "typedef struct _IO_FILE __FILE;": "", - "struct _IO_FILE;": "", - "typedef struct _IO_FILE FILE;": "typedef int FILE;", - "typedef struct _IO_FILE": "typedef int", - "typedef struct __locale_struct": "typedef int", - "typedef __locale_t locale_t;": "typedef int locale_t;", - "struct _IO_FILE_plus;": "", - "typedef _G_fpos_t": "typedef int", - "typedef struct[^\n]*\n{[^}]*}[^;]*;": "", - "typedef struct[^{]{[^}]*}": "typedef int", - "struct _IO_FILE": "int", + # "struct[^}]*};": "", + # " struct[^{]*[^}]*}[^;]*;": "", + # "typedef struct _IO_FILE __FILE;": "", + # "struct _IO_FILE;": "", + # "typedef struct _IO_FILE FILE;": "typedef int FILE;", + # "typedef struct _IO_FILE": "typedef int", + # "typedef struct __locale_struct": "typedef int", + # "typedef __locale_t locale_t;": "typedef int locale_t;", + # "struct _IO_FILE_plus;": "", + # "typedef _G_fpos_t": "typedef int", + # "typedef struct[^\n]*\n{[^}]*}[^;]*;": "", + # "typedef struct[^{]{[^}]*}": "typedef int", + # "struct _IO_FILE": "int", } CSMITH_DIR = "csmith-2.3.0" @@ -229,7 +233,7 @@ def fuzz(tests_dir, fuzz_arg, num_iter): try: args = ["cargo", "run", "--release", "--bin", "fuzz", "--", fuzz_arg, os.path.join(tests_dir, "test_polished.c")] proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir) - proc.communicate(timeout=10) + proc.communicate(timeout=60) if proc.returncode != 0: raise Exception("Test `{}` failed with exit code {}.".format(" ".join(args), proc.returncode)) except subprocess.TimeoutExpired as e: