123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218(**************************************************************************)(* This file is part of the Codex semantics library. *)(* *)(* Copyright (C) 2013-2025 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file LICENSE). *)(* *)(**************************************************************************)moduleLog=Tracelog.Make(structletcategory="Domains.Overflow_checks"end);;moduleIn_bits=Units.In_bitsopenOperator.Alarmletincrease_size=Codex_config.extend_size_for_additive_operationsmoduleMake(Sub:Sig.BASE_WITH_INTEGER)=structincludeSub(* TODO: Ideally, we would emit alarms here otherwise some assumptions could go unnoticed. This requires
that every new pointer is assumed to be in the correct range, which we don't do for now. *)letshould_emit_alarm=falseletemit_unsigned_signed_alarms=falsemoduleBinary_Forward=structincludeSub.Binary_Forward(* TODO: Should be provided in the domain signature; assume true
is a workaround. *)letclonedom=letdom'=matchSub.assumedom(Sub.Boolean_Forward.true_dom)with|Somex->x|None->assertfalseindom'(* Evaluate a condition in a copy of dom (to not pollute it with
new terms), that is removed if the condition is
valid. Otherwise, calls [emit_alarm] then refine dom assuming
that the condition is true.
"valid" means that the condition does not return false if [id_or_not] is id,
and does not return true if it is Quadrivalent.not. *)letcheck_condmkcondemit_alarmdom=letdom'=clonedominletcond=mkconddom'inmatchquery_booleandom'condwith|Lattices.Quadrivalent.True->()|Lattices.Quadrivalent.Bottom->Log.fatal(funp->p"Evaluation of an overflow condition returned a bottom. \
This is probably an analyzer error that should not have happened \
(the bottom should have been catched earlier). Please report.")|Lattices.Quadrivalent.(False|Top)->emit_alarm();beginmatchassumedom'cond(* Further refine the error. *)with|None->raiseSig.Bottom|Somedom''->Sub.Context.assigndomdom''end(** [check_overflow ~signed ~small_size ~wide_size value]
Adds assertion ensuring that [value], a binary value of size [~wide_size]
bits, can correctly fit on [~small_size] bits without over/underflow.
- If [~signed], this means [-2^{small_size-1} <=s value <=s 2^{small_size-1} - 1]
- If not [~signed], this means [0 <=u value <=u 2^small_size - 1] *)letcheck_overflow~signed~(small_size:In_bits.t)~wide_sizeoperationvaluedom=letcompare=(ifsignedthenbisleelsebiule)inlettwo_pow_size=Z.shift_leftZ.one(ifsignedthen(small_size:>int)-1else(small_size:>int))in(* Upper bound *)letupper_bound=Z.subtwo_pow_sizeZ.oneinletmkconddom=letupper_bound_val=biconst~size:wide_sizeupper_bounddomincompare~size:wide_sizedomvalueupper_bound_valinletemit_alarm()=ifshould_emit_alarmthenletsize=small_sizeinletoverflow_type=ifsignedthenSignedelseUnsignedinEmit_alarm.emit_alarm(Integer_overflow{size;operation;overflow_type})incheck_condmkcondemit_alarmdom;(* lower bound *)ifsignedthenletlower_bound=Z.negtwo_pow_sizeinletmkconddom=letlower_bound_val=biconst~size:wide_sizelower_bounddomincompare~size:wide_sizedomlower_bound_valvalueinletemit_alarm()=ifshould_emit_alarmthenbeginletsize=small_sizeinletoverflow_type=SignedinEmit_alarm.emit_alarm(Integer_underflow{size;operation;overflow_type})endincheck_condmkcondemit_alarmdom(** [term_builder ~signed dom ~small_size ~wide_size binop v1 v2 r]
extends [v1], [v2] from [~small_size] to [~wide_size], computes the
[binop] on [~wide_size], then assumes there are no overflows. *)letterm_builder~signeddom~small_size~wide_sizebinopopv1v2=letdom=clonedominletextend=ifsignedthenbsextelsebuextinletv1_wide=extend~size:wide_size~oldsize:small_sizedomv1inletv2_wide=extend~size:wide_size~oldsize:small_sizedomv2inletres=binop~size:wide_sizedomv1_widev2_wideincheck_overflow~signed~small_size~wide_sizeopresdom(** Variant of [term_builder] (with inlined [check_overflow])
for the [~nusw] case (adding an unsigned (pointer) and a signed [offset])
result must fit on [unsigned] without overflow or underflow. *)letcheck_nusworig_dom~small_size~wide_sizebinopoperationv1v2=letdom=cloneorig_dominletoverflow_type=Operator.Alarm.Unsigned_signedinletv1_wide=buext~size:wide_size~oldsize:small_sizedomv1inletv2_wide=bsext~size:wide_size~oldsize:small_sizedomv2inletres=binop~size:wide_sizedomv1_widev2_wideinlettwo_pow_size=Z.shift_leftZ.one(small_size:>int)in(* Upper bound *)letmkconddom=letupper_bound=Z.subtwo_pow_sizeZ.oneinletupper_bound_val=biconst~size:wide_sizeupper_bounddominbisle~size:wide_sizedomresupper_bound_valinletemit_alarm()=letsize=small_sizeinifemit_unsigned_signed_alarmsthenEmit_alarm.emit_alarm(Integer_overflow{overflow_type;size;operation})incheck_condmkcondemit_alarmdom;(* lower bound *)letmkconddom=letlower_bound_val=biconst~size:wide_sizeZ.zerodominbisle~size:wide_sizedomlower_bound_valresinletemit_alarm()=letsize=small_sizeinifemit_unsigned_signed_alarmsthenEmit_alarm.emit_alarm(Integer_underflow{overflow_type;size;operation});incheck_condmkcondemit_alarmdom(** [binop_with_overflow_guard ~small_size ~wide_size binop_func v1 v2]
returns [binop_func ~size:small_size v1 v2], but first it ensures that no
overflow occurs by computing [binop_func ~size:wide_size v1 v2] and checking
that it fits on [~small_size] bits.
@param ~small_size should be the size of terms [v1] and [v2]
@param ~wide_size should be large enough to ensure that
[binop_func ~size:wide_size v1 v2] does not overflow *)letbinop_with_overflow_guard~nsw~nuw~nuswdom~small_size~wide_sizebinopopv1v2=ifnswthenterm_builder~signed:truedom~small_size~wide_sizebinopopv1v2;ifnuwthenterm_builder~signed:falsedom~small_size~wide_sizebinopopv1v2;ifnusw(* We need size+1 here to be able to sign compare unsigned values *)thencheck_nuswdom~small_size~wide_size:(increase_sizewide_size)binopopv1v2;binop~size:small_sizedomv1v2letbiadd~size~flagsdomab=letOperator.Flags.Biadd.{nsw;nuw;nusw}=Operator.Flags.Biadd.unpackflagsin(* if nsw || nuw || nusw then
Emit_alarm.feedback "OverflowAddition size:%d nsw:%b nuw:%b nusw:%b (%a + %a = %a)"
size nsw nuw nusw
Terms.pretty a Terms.pretty b Terms.pretty res; *)binop_with_overflow_guard~nsw~nuw~nuswdom~small_size:size~wide_size:(increase_sizesize)(* No overflow on n+1 bits *)(Binary_Forward.biadd~flags)Biaddabletbisub~size~flagsdomab=letOperator.Flags.Bisub.{nsw;nuw;nusw}=Operator.Flags.Bisub.unpackflagsin(* if nsw || nuw || nusw then
Emit_alarm.feedback "OverflowSubtraction size:%d nsw:%b nuw:%b nusw:%b (%a + %a = %a)"
size nsw nuw nusw
Terms.pretty a Terms.pretty b Terms.pretty res; *)binop_with_overflow_guard~nsw~nuw~nuswdom~small_size:size~wide_size:(increase_sizesize)(* No overflow on n+1 bits *)(Binary_Forward.bisub~flags)Bisubabletbimul~size~flagsdomab=(* Binary_Forward.bimul ~size ~nsw ~nuw dom a b *)letOperator.Flags.Bimul.{nsw;nuw}=Operator.Flags.Bimul.unpackflagsinbinop_with_overflow_guard~nsw~nuw~nusw:falsedom~small_size:size~wide_size:In_bits.(doublesize)(* No overflow on 2n bits *)(Binary_Forward.bimul~flags)Bimulab(* There is only one case whe div overflows: MIN_INT / -1 *)letbisdiv~sizedomab=letmkconddom=letminus_one=biconst~sizeZ.minus_onedominletdiv_is_neg_one=beq~sizedombminus_oneinletmin_int=biconst~size(Z.neg(Z.shift_leftZ.one((size:>int)-1)))dominletnum_is_min_int=beq~sizedomamin_intinletcond=Boolean_Forward.(&&)domdiv_is_neg_onenum_is_min_intinBoolean_Forward.notdomcondinletemit_alarm()=ifshould_emit_alarmthenbeginletoverflow_type=Signedinletalarm=Integer_overflow{overflow_type;size;operation=Bisdiv}inEmit_alarm.emit_alarmalarmendincheck_condmkcondemit_alarmdom;bisdiv~sizedomab;;endend