Challenge 8 – Dynamic Dispatching, Dead Code and Floats

With electronic voting adding up to the heat of election nights, every citizen might be interested in getting some strong assurance that the machine is doing its job, especially since there might be a disclaimer that “no warranty is given on the exactitude of the next president chosen by the software”. Here, you have the source code to check it for yourself!

Have a look at the following code and see if you can spot the errors. Once you think you’ve got them all, click on the “Go CodePeer” button to see how well you matched up to CodePeer’s comprehensive and rapid analysis of the same code.

Error line 71: “warning: useless self assignment into Win”

Here’s how CodePeer helps you coming to this conclusion: CodePeer detects that Win is reassigned its own value. Indeed, Win is initialized to True on line 60, so assigning it to True on line 71 is at best useless, at worst an error. Here, it is the latter: Win should be assigned False, which got the more votes.

Error line 86: “warning: useless self assignment into Scores(Vote)”

Here’s how CodePeer helps you coming to this conclusion: Reviewing the corresponding line shows immediately the error: Scores(Vote) is assigned to itself! As we are counting votes here, Scores(Vote) should really be incremented, so that a “+1″ was meant.

Error line 106: “warning: dead code because Fraction – 5_452_595/8_388_608 in (-Inf..-1_258_291/8_388_608]”

Here’s how CodePeer helps you coming to this conclusion: CodePeer detects that the test for this branch of the if-statement is always false. Indeed, execution reaches this point only when the first test is false, so that Fraction is less than or equal to 0.5. Then, Fraction cannot be greater than 0.65. The order of the tests should be changed. The strange fractions mentioned in the warning come from the machine representation of 0.5 and 0.65, which are close to these real numbers, but not exactly equal. (5_452_595/8_388_608 = 0,649999976)

Error line 128: “high: precondition failure on voting.analyze: requires M.Score to be initialized”

Here’s how CodePeer helps you coming to this conclusion: Because function Analyze reads its parameter M.Score, it requires in its precondition that M.Score is initialized (output as a pseudo Ada attribute M.Score’Initialized). This should be set by the dispatching call to procedure Count. Looking at the two overriding procedures Count, none sets the value of component Score.

package Voting is

   type Id is new Integer;               --  machine summary of a human being
   type Candidate_Pos is range 1 .. 20;  --  number identifying a candidate

   type Kind is (Referendum, Election);                --  type of vote
   type Ballot is private;                             --  human input
   type Ballots is array (Id range <>) of Ballot;

   type Machine (Population : Id) is tagged private;   --  voting machinery
   type Referendum_Machine is new Machine with private;
   type Election_Machine is new Machine with private;

   --  Store individual votes
   procedure Get_Votes (M : in out Machine; Votes : Ballots);

   --  Determine the winner of the vote by counting her supporters
   procedure Count (M : in out Machine) is null;
   procedure Count (M : in out Referendum_Machine);
   procedure Count (M : in out Election_Machine);

   --  Analyze the result of the vote for tomorrow's headlines
   type Analysis is (Coalition, Majority, Landslide);
   function Analyze (M : Machine) return Analysis;

private

   type Ballot is record
      Voter    : Id;
      Decision : Boolean;
      Choice   : Candidate_Pos;
   end record;

   type Machine (Population : Id) is tagged record
      Votes : Ballots (1 .. Population);
      Score : Positive;
   end record;

   type Referendum_Machine is new Machine with record
      Winner : Boolean;
   end record;

   type Election_Machine is new Machine with record
      Winner : Candidate_Pos;
   end record;

end Voting;

package body Voting is

   procedure Get_Votes (M : in out Machine; Votes : Ballots) is
   begin
      for Voter in Votes'Range loop
         M.Votes (Voter) := Votes (Voter);
      end loop;
   end Get_Votes;

   procedure Count (M : in out Referendum_Machine) is
      Scores : array (Boolean) of Natural := (others => 0);
      Win    : Boolean := True;
   begin
      for B in 1 .. M.Population loop
         declare
            Vote : constant Boolean := M.Votes (B).Decision;
         begin
            Scores (Vote) := Scores (Vote) + 1;
         end;
      end loop;

      if Scores (True) < Scores (False) then
         Win := True;
      end if;

      M.Winner := Win;
   end Count;

   procedure Count (M : in out Election_Machine) is
      Scores : array (Candidate_Pos) of Natural := (others => 0);
      Win    : Candidate_Pos := 1;
      High   : Natural := 0;
   begin
      for B in 1 .. M.Population loop
         declare
            Vote : constant Candidate_Pos := M.Votes (B).Choice;
         begin
            Scores (Vote) := Scores (Vote);
         end;
      end loop;

      for C in Candidate_Pos loop
         if Scores (C) > High then
            Win  := C;
            High := Scores (C);
         end if;
      end loop;

      M.Winner := Win;
   end Count;

   function Analyze (M : Machine) return Analysis is
      Fraction : constant Float := Float (M.Score) / Float (M.Population);
   begin
      if Fraction > 0.5 then
         return Majority;
      elsif Fraction > 0.65 then
         return Landslide;
      else
         return Coalition;
      end if;
   end Analyze;

end Voting;

with Voting; use Voting;

function Polls (K : Kind; Population : Id; Votes : Ballots) return Analysis
is
   M : access Machine'Class;
begin
   --  Initialize the machine
   case K is
      when Referendum => M := new Referendum_Machine (Population);
      when Election   => M := new Election_Machine (Population);
   end case;

   M.Get_Votes (Votes);  --  Feed the machine with votes
   M.Count;              --  Count the votes
   return M.Analyze;     --  Prepare for the media frenzy over the results
end Polls;