Skip to content

Commit c33ad6a

Browse files
committed
Checks: Add Ada Checks ADA95_5.5.1, ADA95_5.5.6
1 parent ea852d3 commit c33ad6a

6 files changed

Lines changed: 506 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
ERR1 = "Use 'Range or the subtype name of the range instead of Type'First .. Type'Last"
2+
3+
4+
def ids():
5+
return ("ADA95_5.5.1",)
6+
7+
8+
def name(id):
9+
return {
10+
"ADA95_5.5.1": "Published Standards/Ada 95/5.5.1 Range Values",
11+
}[id]
12+
13+
14+
def tags(id):
15+
return {
16+
"ADA95_5.5.1": [
17+
"Language: Ada",
18+
"Standard: Ada 95",
19+
"Expressions",
20+
],
21+
}.get(id, [])
22+
23+
24+
def detailed_description(id):
25+
return {
26+
"ADA95_5.5.1": """
27+
<p><b>Guideline</b></p>
28+
<p>&#8226; Use <code>'First</code> or <code>'Last</code> instead of numeric literals to represent the first or last values of a range.</p>
29+
<p>&#8226; Use <code>'Range</code> or the subtype name of the range instead of <code>'First .. 'Last</code>.</p>
30+
31+
<p><b>Example</b></p>
32+
<pre><code language="Ada">type Temperature is range All_Time_Low .. All_Time_High;
33+
type Weather_Stations is range 1 .. Max_Stations;
34+
Current_Temperature : Temperature := 60;
35+
Offset : Temperature;
36+
...
37+
for I in Weather_Stations loop
38+
Offset := Current_Temperature - Temperature'First;
39+
...
40+
end loop;
41+
</code></pre>
42+
43+
<p><b>Rationale</b></p>
44+
<p>In the example above, it is better to use <code>Weather_Stations</code> in the <code>for</code> loop than to use <code>Weather_Stations'First .. Weather_Stations'Last</code> or <code>1 .. Max_Stations</code> because it is clearer, less error-prone, and less dependent on the definition of the type <code>Weather_Stations</code>. Similarly, it is better to use <code>Temperature'First</code> in the offset calculation than to use <code>All_Time_Low</code> because the code will still be correct if the definition of the subtype <code>Temperature</code> is changed. This enhances program reliability.</p>
45+
46+
<p><b>Caution</b></p>
47+
<p>When you implicitly specify ranges and attributes like this, be careful that you use the correct subtype name. It is easy to refer to a very large range without realizing it. For example, given the declarations:</p>
48+
<pre><code language="Ada">type Large_Range is new Integer;
49+
subtype Small_Range is Large_Range range 1 .. 10;
50+
type Large_Array is array (Large_Range) of Integer;
51+
type Small_Array is array (Small_Range) of Integer;
52+
</code></pre>
53+
<p>then the first declaration below works fine, but the second one is probably an accident and raises an exception on most machines because it is requesting a huge array (indexed from the smallest integer to the largest one):</p>
54+
<pre><code language="Ada">Array_1 : Small_Array;
55+
Array_2 : Large_Array;
56+
</code></pre>
57+
58+
<p><b>Developer's Note</b></p>
59+
<p>Guideline 1 (use <code>'First</code>/<code>'Last</code> instead of numeric literals) is not automated: determining whether a numeric literal could be replaced by a named attribute requires full type inference across the type hierarchy, which cannot be determined statically.</p>
60+
""",
61+
}[id]
62+
63+
64+
def test_entity(file):
65+
return file.kind().check("Ada File")
66+
67+
68+
def check(check, file):
69+
lex = file.lexer(lookup_ents=False).first()
70+
71+
while lex:
72+
if lex.token() == "Identifier":
73+
type_name = lex.text()
74+
75+
next_lex = lex.next(True, True)
76+
if not (next_lex and next_lex.text() == "'"):
77+
lex = lex.next(True, True)
78+
continue
79+
80+
next_lex = next_lex.next(True, True)
81+
if not (next_lex and next_lex.text().lower() == "first"):
82+
lex = lex.next(True, True)
83+
continue
84+
85+
next_lex = next_lex.next(True, True)
86+
if not (next_lex and next_lex.text() == ".."):
87+
lex = lex.next(True, True)
88+
continue
89+
90+
next_lex = next_lex.next(True, True)
91+
if not (next_lex and next_lex.text() == type_name):
92+
lex = lex.next(True, True)
93+
continue
94+
95+
next_lex = next_lex.next(True, True)
96+
if not (next_lex and next_lex.text() == "'"):
97+
lex = lex.next(True, True)
98+
continue
99+
100+
next_lex = next_lex.next(True, True)
101+
if not (next_lex and next_lex.text().lower() == "last"):
102+
lex = lex.next(True, True)
103+
continue
104+
105+
check.violation(None, file, lex.line_begin(), lex.column_begin(), ERR1)
106+
107+
lex = lex.next(True, True)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package body Test_5_5_1 is
2+
3+
procedure Do_Work is
4+
type Temp_Values is array (Temperature) of Integer;
5+
Vals : Temp_Values := (others => 0);
6+
Slice : Temp_Values;
7+
X : Integer := 0;
8+
I : Temperature := Temperature'First;
9+
begin
10+
-- Violation: for loop using T'First .. T'Last
11+
for J in Temperature'First .. Temperature'Last loop -- UndCC_Violation
12+
X := Integer(J);
13+
end loop;
14+
15+
-- Valid: for loop using 'Range
16+
for J in Temperature'Range loop -- UndCC_Valid
17+
X := Integer(J);
18+
end loop;
19+
20+
-- Valid: for loop using type name
21+
for J in Temperature loop -- UndCC_Valid
22+
X := Integer(J);
23+
end loop;
24+
25+
-- Violation: reverse for loop using T'First .. T'Last
26+
for J in reverse Temperature'First .. Temperature'Last loop -- UndCC_Violation
27+
X := Integer(J);
28+
end loop;
29+
30+
-- Violation: membership test using T'First .. T'Last
31+
if I in Temperature'First .. Temperature'Last then -- UndCC_Violation
32+
null;
33+
end if;
34+
35+
-- Valid: membership test using 'Range
36+
if I in Temperature'Range then -- UndCC_Valid
37+
null;
38+
end if;
39+
40+
-- Violation: array slice using T'First .. T'Last
41+
Slice := Vals(Temperature'First .. Temperature'Last); -- UndCC_Violation
42+
43+
-- Valid: array slice using 'Range
44+
Slice := Vals(Temperature'Range); -- UndCC_Valid
45+
46+
-- Violation: multi-line T'First .. T'Last (whitespace is skipped)
47+
for J in
48+
Temperature'First .. -- UndCC_Violation
49+
Temperature'Last loop
50+
null;
51+
end loop;
52+
53+
-- Violation: Color enum range
54+
for C in Color'First .. Color'Last loop -- UndCC_Violation
55+
null;
56+
end loop;
57+
58+
-- Valid: Color using 'Range
59+
for C in Color'Range loop -- UndCC_Valid
60+
null;
61+
end loop;
62+
63+
-- Valid: type-converted bounds are a different pattern (Integer range, not Temperature)
64+
if X in Integer(Temperature'First) .. Integer(Temperature'Last) then -- UndCC_Valid
65+
null;
66+
end if;
67+
68+
-- Valid: only 'First used standalone (no .. follows)
69+
X := Integer(Temperature'First); -- UndCC_Valid
70+
71+
-- Valid: only 'Last used standalone
72+
X := Integer(Temperature'Last); -- UndCC_Valid
73+
74+
-- Valid: 'Pred attribute (not 'First/'Last, should not trigger)
75+
X := Integer(Color'Pos(Color'Pred(Color'Last))); -- UndCC_Valid
76+
end Do_Work;
77+
78+
end Test_5_5_1;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package Test_5_5_1 is
2+
3+
type Temperature is range -50 .. 150;
4+
type Color is (Red, Green, Blue);
5+
type Index is range 1 .. 100;
6+
7+
-- Violation: T'First .. T'Last in subtype constraint
8+
subtype Temp_Full is Temperature range Temperature'First .. Temperature'Last; -- UndCC_Violation
9+
10+
-- Valid: using 'Range
11+
subtype Temp_Range is Temperature range Temperature'Range; -- UndCC_Valid
12+
13+
-- Valid: using type name directly
14+
subtype Temp_All is Temperature; -- UndCC_Valid
15+
16+
-- Valid: only 'First (not the full pattern)
17+
subtype Temp_Pos is Temperature range 0 .. Temperature'Last; -- UndCC_Valid
18+
19+
-- Valid: only 'Last (not the full pattern)
20+
subtype Temp_Neg is Temperature range Temperature'First .. 0; -- UndCC_Valid
21+
22+
-- Violation: array index using T'First .. T'Last
23+
type Temp_Array is array (Temperature'First .. Temperature'Last) of Integer; -- UndCC_Violation
24+
25+
-- Valid: array index using 'Range
26+
type Temp_Array_Good is array (Temperature'Range) of Integer; -- UndCC_Valid
27+
28+
-- Valid: array index using type name
29+
type Color_Array is array (Color) of Integer; -- UndCC_Valid
30+
31+
-- Violation: enumeration type
32+
subtype Color_Full is Color range Color'First .. Color'Last; -- UndCC_Violation
33+
34+
-- Valid: using 'Range for enum
35+
subtype Color_Good is Color range Color'Range; -- UndCC_Valid
36+
37+
-- Violation: attribute names in different case (Ada is case-insensitive)
38+
subtype Index_Full is Index range Index'FIRST .. Index'LAST; -- UndCC_Violation
39+
40+
-- Valid: different type names on each side
41+
type Mixed_Array is array (Color'First .. Index'Last) of Boolean; -- UndCC_Valid
42+
43+
-- Violation: built-in Integer type (the "huge range" caution from the guideline)
44+
subtype Big_Int is Integer range Integer'First .. Integer'Last; -- UndCC_Violation
45+
46+
-- Valid: qualified expression uses apostrophe differently (not an attribute access)
47+
Sixty : constant Temperature := Temperature'(60); -- UndCC_Valid
48+
49+
end Test_5_5_1;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
ERR1 = "Use <= or >= instead of = when comparing real operands"
2+
3+
4+
def ids():
5+
return ("ADA95_5.5.6",)
6+
7+
8+
def name(id):
9+
return {
10+
"ADA95_5.5.6": "Published Standards/Ada 95/5.5.6 Accuracy of Operations With Real Operands",
11+
}[id]
12+
13+
14+
def tags(id):
15+
return {
16+
"ADA95_5.5.6": [
17+
"Language: Ada",
18+
"Standard: Ada 95",
19+
"Expressions",
20+
],
21+
}.get(id, [])
22+
23+
24+
def detailed_description(id):
25+
return {
26+
"ADA95_5.5.6": """
27+
<p><b>Guideline</b></p>
28+
<p>&#8226; Use <code>&lt;=</code> and <code>&gt;=</code> in relational expressions with real operands instead of <code>=</code>.</p>
29+
30+
<p><b>Example</b></p>
31+
<pre><code language="Ada">Current_Temperature : Temperature := 0.0;
32+
Temperature_Increment : Temperature := 1.0 / 3.0;
33+
Maximum_Temperature : constant := 100.0;
34+
...
35+
loop
36+
...
37+
Current_Temperature :=
38+
Current_Temperature + Temperature_Increment;
39+
...
40+
exit when Current_Temperature >= Maximum_Temperature;
41+
...
42+
end loop;
43+
</code></pre>
44+
45+
<p><b>Rationale</b></p>
46+
<p>Fixed- and floating-point values, even if derived from similar expressions, may not be exactly equal. The imprecise, finite representations of real numbers in hardware always have round-off errors so that any variation in the construction path or history of two real numbers has the potential for resulting in different numbers, even when the paths or histories are mathematically equivalent.</p>
47+
<p>The Ada definition of model intervals also means that the use of <code>&lt;=</code> is more portable than either <code>&lt;</code> or <code>=</code>.</p>
48+
49+
<p><b>Notes</b></p>
50+
<p>Floating-point arithmetic is treated in Guideline 7.2.7.</p>
51+
52+
<p><b>Exceptions</b></p>
53+
<p>If your application must test for an exact value of a real number (e.g., testing the precision of the arithmetic on a certain machine), then the <code>=</code> would have to be used. But never use <code>=</code> on real operands as a condition to exit a loop.</p>
54+
55+
<p><b>Developer's Note</b></p>
56+
<p>The check may produce false negatives when both operands are complex expressions (e.g., function calls or arithmetic subexpressions) rather than named variables or parameters, since the real type cannot be determined without evaluating the full expression type.</p>
57+
""",
58+
}[id]
59+
60+
61+
def test_entity(file):
62+
return file.kind().check("Ada File")
63+
64+
65+
def _is_real_type(ent):
66+
if not ent:
67+
return False
68+
69+
type_ref = ent.ref("Typed", "Type")
70+
if not type_ref:
71+
return False
72+
73+
type_ent = type_ref.ent()
74+
75+
while type_ent:
76+
type_def = str(type_ent.type()).lower() if type_ent.type() else ""
77+
if "digits" in type_def or "delta" in type_def:
78+
return True
79+
derive_ref = type_ent.ref("Derivefrom")
80+
type_ent = derive_ref.ent() if derive_ref else None
81+
82+
return False
83+
84+
85+
def check(check, file):
86+
lex = file.lexer().first()
87+
88+
while lex:
89+
if lex.token() == "Operator" and lex.text() == "=":
90+
left_lex = lex.previous(True, True)
91+
left_ent = (
92+
left_lex.ent()
93+
if left_lex and left_lex.token() == "Identifier"
94+
else None
95+
)
96+
97+
right_lex = lex.next(True, True)
98+
right_ent = (
99+
right_lex.ent()
100+
if right_lex and right_lex.token() == "Identifier"
101+
else None
102+
)
103+
104+
if _is_real_type(left_ent) or _is_real_type(right_ent):
105+
check.violation(
106+
left_ent or right_ent,
107+
file,
108+
lex.line_begin(),
109+
lex.column_begin(),
110+
ERR1,
111+
)
112+
113+
lex = lex.next(True, True)

0 commit comments

Comments
 (0)