Well, GNAT does emit a warning:
warning: variable "Time_Two" is read but never assigned
The warning can be converted into an error by placing the configuration pragma Warning_As_Error
either at the very top of main.adb
or in a configuration file gnat.adc
[GNAT RM 2.205]
pragma Warning_As_Error ("*never assigned");
Dealing with uninitialized variables is a common source of errors and some additional background information on this topic (with a particular a focus on using run-time checks, as asked) is available in the paper
Exposing Uninitialized Variables: Strengthening and Extending Run-Time Checks in Ada
Interestingly, placing the configuration pragma Initialize_Scalars
[GNAT RM 2.88] at the very top of main.adb
yields (for this particular case) a run-time exception as Times_Two
is initialized with Long_Long_Integer'First
which appears to be invalid for Ada.Calendar.Time
(In GNAT, Long_Long_Integer
is the underlying type of Ada.Calendar.Time
, see a-calend.ads
):
$ /main
Time one: 2019-06-27 19:46:54
raised ADA.CALENDAR.TIME_ERROR : a-calend.adb:611
Of course, an invalid value may not exist or may have a different value. See the link to GNAT RM and the paper for more info on the usage of Initialize_Scalars
. See also the related pragma Normalize_Scalars
[GNAT RM 2.122].
An alternative (static) method to detect uninitialized variables is to use SPARK. An attempt to proof the correctness of main.adb
yields:
high: "Time_Two" is not initialized.
UPDATE 1
Here's a minimal example of how one can use the pragma Initialize_Scalars
together with GNAT compiler switches that insert data valdiation checkers at particular points in the code:
main.adb
-- Ignore compile time warning for the sake of demonstration.
pragma Warnings (Off, "*never assigned");
pragma Initialize_Scalars;
with Ada.Text_IO;
procedure Main is
package Foo is
type Bar is private;
procedure Put_Bar (B : Bar);
private
type Bar is new Integer range -20 .. 20;
end Foo;
package body Foo is
procedure Put_Bar (B : Bar) is
begin
-- (2) Constraint_Error is raised if switch "-gnatVDo" (Validate
-- Operator and Attribute Operands) is used during compilation.
-- This switch effectively inserts the code
--
-- if not B'Valid then
-- raise Constraint_Error with "invalid data";
-- end if;
--
-- just before B'Image is evaluated. As the value Integer'First
-- is not in Bar'Range, B'Valid returns False and the
-- exception is raised.
--
-- See also in GPS IDE:
--
-- Edit > Project Properties... > Build > Switches > Ada
-- and then "Validity Checking Mode".
Ada.Text_IO.Put_Line (B'Image);
end Put_Bar;
end Foo;
-- (1) Because pragma "Initialize_Scalars" is used, B is deterministically
-- initialized to Integer'First. This behavior is inherited from the
-- configuration pragma "Normalize_Scalars" (see GNAT RM). Here,
-- Integer'First happens to be invalid as it falls outside the
-- range of subtype Foo.Bar (which is -20 .. 20).
B : Foo.Bar;
begin
Foo.Put_Bar (B);
end Main;
UPDATE 2
Second example in response to the feedback in the comments below (I misinterpreted the question):
main.adb
with Ada.Calendar;
with Ada.Text_IO;
with GNAT.Calendar.Time_IO;
procedure Main is
type Optional_Time (Valid : Boolean := False) is
record
case Valid is
when False =>
null;
when True =>
Value : Ada.Calendar.Time;
end case;
end record;
-----------
-- Image --
-----------
function Image (OT : Optional_Time) return String is
Format : constant GNAT.Calendar.Time_IO.Picture_String := "%Y-%m-%d %H:%M:%S";
begin
if OT.Valid then
return GNAT.Calendar.Time_IO.Image (OT.Value, Format);
else
return "<Invalid>";
end if;
end Image;
Time : Optional_Time;
begin
Ada.Text_IO.Put_Line (Image (Time));
Time := (Valid => True, Value => Ada.Calendar.Clock);
Ada.Text_IO.Put_Line (Image (Time));
Time := (Valid => False);
Ada.Text_IO.Put_Line (Image (Time));
end Main;
-gnatwe
treats warnings as errors. Of course, that’s a lot more drastic than the pragma approach here. At run time, there’s also’Valid
; doesn't help here, sinceTime
isn’t scalar (I suppose because it’s private). – Capsize