diff --git a/src/librustc_mir/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs index 8de85aab6e3..2ba18359e02 100644 --- a/src/librustc_mir/transform/const_prop.rs +++ b/src/librustc_mir/transform/const_prop.rs @@ -469,6 +469,121 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { } } + fn check_unary_op(&mut self, arg: &Operand<'tcx>, source_info: SourceInfo) -> Option<()> { + self.use_ecx(source_info, |this| { + let ty = arg.ty(&this.local_decls, this.tcx); + + if ty.is_integral() { + let arg = this.ecx.eval_operand(arg, None)?; + let prim = this.ecx.read_immediate(arg)?; + // Need to do overflow check here: For actual CTFE, MIR + // generation emits code that does this before calling the op. + if prim.to_bits()? == (1 << (prim.layout.size.bits() - 1)) { + throw_panic!(OverflowNeg) + } + } + + Ok(()) + })?; + + Some(()) + } + + fn check_binary_op( + &mut self, + op: BinOp, + left: &Operand<'tcx>, + right: &Operand<'tcx>, + source_info: SourceInfo, + place_layout: TyLayout<'tcx>, + overflow_check: bool, + ) -> Option<()> { + let r = self.use_ecx(source_info, |this| { + this.ecx.read_immediate(this.ecx.eval_operand(right, None)?) + })?; + if op == BinOp::Shr || op == BinOp::Shl { + let left_bits = place_layout.size.bits(); + let right_size = r.layout.size; + let r_bits = r.to_scalar().and_then(|r| r.to_bits(right_size)); + if r_bits.map_or(false, |b| b >= left_bits as u128) { + let lint_root = self.lint_root(source_info)?; + let dir = if op == BinOp::Shr { "right" } else { "left" }; + self.tcx.lint_hir( + ::rustc::lint::builtin::EXCEEDING_BITSHIFTS, + lint_root, + source_info.span, + &format!("attempt to shift {} with overflow", dir), + ); + return None; + } + } + + // If overflow checking is enabled (like in debug mode by default), + // then we'll already catch overflow when we evaluate the `Assert` statement + // in MIR. However, if overflow checking is disabled, then there won't be any + // `Assert` statement and so we have to do additional checking here. + if !overflow_check { + self.use_ecx(source_info, |this| { + let l = this.ecx.read_immediate(this.ecx.eval_operand(left, None)?)?; + let (_, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?; + + if overflow { + let err = err_panic!(Overflow(op)).into(); + return Err(err); + } + + Ok(()) + })?; + } + + Some(()) + } + + fn check_cast( + &mut self, + op: &Operand<'tcx>, + ty: Ty<'tcx>, + source_info: SourceInfo, + place_layout: TyLayout<'tcx>, + ) -> Option<()> { + if ty.is_integral() && op.ty(&self.local_decls, self.tcx).is_integral() { + let value = self.use_ecx(source_info, |this| { + this.ecx.read_immediate(this.ecx.eval_operand(op, None)?) + })?; + + // Do not try to read bits for ZSTs + if !value.layout.is_zst() { + let value_size = value.layout.size; + let value_bits = value.to_scalar().and_then(|r| r.to_bits(value_size)); + if let Ok(value_bits) = value_bits { + let truncated = truncate(value_bits, place_layout.size); + if truncated != value_bits { + let scope = source_info.scope; + let lint_root = match &self.source_scopes[scope].local_data { + ClearCrossCrate::Set(data) => data.lint_root, + ClearCrossCrate::Clear => return None, + }; + self.tcx.lint_hir( + ::rustc::lint::builtin::CONST_ERR, + lint_root, + source_info.span, + &format!( + "truncating cast: the value {} requires {} bits but \ + the target type is only {} bits", + value_bits, + value_size.bits(), + place_layout.size.bits() + ), + ); + return None; + } + } + } + } + + Some(()) + } + fn const_prop( &mut self, rvalue: &Rvalue<'tcx>, @@ -476,8 +591,6 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { source_info: SourceInfo, place: &Place<'tcx>, ) -> Option<()> { - let span = source_info.span; - // #66397: Don't try to eval into large places as that can cause an OOM if place_layout.size >= Size::from_bytes(MAX_ALLOC_LIMIT) { return None; @@ -498,66 +611,14 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { // if an overflow would occur. Rvalue::UnaryOp(UnOp::Neg, arg) if !overflow_check => { trace!("checking UnaryOp(op = Neg, arg = {:?})", arg); - - self.use_ecx(source_info, |this| { - let ty = arg.ty(&this.local_decls, this.tcx); - - if ty.is_integral() { - let arg = this.ecx.eval_operand(arg, None)?; - let prim = this.ecx.read_immediate(arg)?; - // Need to do overflow check here: For actual CTFE, MIR - // generation emits code that does this before calling the op. - if prim.to_bits()? == (1 << (prim.layout.size.bits() - 1)) { - throw_panic!(OverflowNeg) - } - } - - Ok(()) - })?; + self.check_unary_op(arg, source_info)?; } // Additional checking: check for overflows on integer binary operations and report // them to the user as lints. Rvalue::BinaryOp(op, left, right) => { trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right); - - let r = self.use_ecx(source_info, |this| { - this.ecx.read_immediate(this.ecx.eval_operand(right, None)?) - })?; - if *op == BinOp::Shr || *op == BinOp::Shl { - let left_bits = place_layout.size.bits(); - let right_size = r.layout.size; - let r_bits = r.to_scalar().and_then(|r| r.to_bits(right_size)); - if r_bits.map_or(false, |b| b >= left_bits as u128) { - let lint_root = self.lint_root(source_info)?; - let dir = if *op == BinOp::Shr { "right" } else { "left" }; - self.tcx.lint_hir( - ::rustc::lint::builtin::EXCEEDING_BITSHIFTS, - lint_root, - span, - &format!("attempt to shift {} with overflow", dir), - ); - return None; - } - } - - // If overflow checking is enabled (like in debug mode by default), - // then we'll already catch overflow when we evaluate the `Assert` statement - // in MIR. However, if overflow checking is disabled, then there won't be any - // `Assert` statement and so we have to do additional checking here. - if !overflow_check { - self.use_ecx(source_info, |this| { - let l = this.ecx.read_immediate(this.ecx.eval_operand(left, None)?)?; - let (_, overflow, _ty) = this.ecx.overflowing_binary_op(*op, l, r)?; - - if overflow { - let err = err_panic!(Overflow(*op)).into(); - return Err(err); - } - - Ok(()) - })?; - } + self.check_binary_op(*op, left, right, source_info, place_layout, overflow_check)?; } // Work around: avoid ICE in miri. FIXME(wesleywiser) @@ -586,41 +647,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { Rvalue::Cast(CastKind::Misc, op, ty) => { trace!("checking Cast(Misc, {:?}, {:?})", op, ty); - - if ty.is_integral() && op.ty(&self.local_decls, self.tcx).is_integral() { - let value = self.use_ecx(source_info, |this| { - this.ecx.read_immediate(this.ecx.eval_operand(op, None)?) - })?; - - // Do not try to read bits for ZSTs - if !value.layout.is_zst() { - let value_size = value.layout.size; - let value_bits = value.to_scalar().and_then(|r| r.to_bits(value_size)); - if let Ok(value_bits) = value_bits { - let truncated = truncate(value_bits, place_layout.size); - if truncated != value_bits { - let scope = source_info.scope; - let lint_root = match &self.source_scopes[scope].local_data { - ClearCrossCrate::Set(data) => data.lint_root, - ClearCrossCrate::Clear => return None, - }; - self.tcx.lint_hir( - ::rustc::lint::builtin::CONST_ERR, - lint_root, - span, - &format!( - "truncating cast: the value {} requires {} bits but \ - the target type is only {} bits", - value_bits, - value_size.bits(), - place_layout.size.bits() - ), - ); - return None; - } - } - } - } + self.check_cast(op, ty, source_info, place_layout)?; } _ => {}