Skip to content

Commit 4f9d3fe

Browse files
committed
Merge branch 'topic/gnatcheck/import_in_rule_files' into 'master'
Implement features required for LKQL rule files composition Closes #470 See merge request eng/libadalang/langkit-query-language!437
2 parents ec8fc99 + c4c444b commit 4f9d3fe

File tree

55 files changed

+745
-260
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+745
-260
lines changed

lkql_checker/doc/gnatcheck_rm/using_gnatcheck.rst

+81-4
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,13 @@ The following switches control the general ``gnatcheck`` behavior
239239
Specify the charset of the source files. By default, ``ISO-8859-1`` is
240240
used if no charset is specified.
241241

242+
.. index:: --lkql-path
243+
244+
``--lkql-path=dir``
245+
Specify directory to add to the ``LKQL_PATH`` environment variable when
246+
GNATcheck is spawning the LKQL engine. You can specify this option multiple
247+
times to add multiple directories.
248+
242249
.. index:: --rules-dir
243250

244251
``--rules-dir=dir``
@@ -361,6 +368,16 @@ GNATcheck:
361368
file defining this attribute, then, an error is emitted and ``gnatcheck``
362369
will exit with an error code.
363370

371+
``Lkql_Path``
372+
Value is a list of directories to add to the ``LKQL_PATH`` environment
373+
variable when GNATcheck is spawning the LKQL engine. This variable is
374+
used to resolve module importations in LKQL sources. If not absolute, paths
375+
provided through this attribute are relatives to the project file defining
376+
it.
377+
378+
This attributes may work combined with the ``--lkql-path`` switch, in that
379+
case, all directories are added to the ``LKQL_PATH`` environment variable.
380+
364381
``Switches``
365382
Index is a language name. Value is a list of additional switches to be used
366383
when invoking ``gnatcheck``.
@@ -381,6 +398,7 @@ GNATcheck:
381398
* ``-eL``
382399
* ``-r, --rule [rule_name]`` (use ``Rules`` attribute instead)
383400
* ``--rule-file=filename`` (use ``Rule_File`` attribute instead)
401+
* ``--lkql-path=dir`` (use ``Lkql_Path`` attributes instead)
384402
* ``--target`` (use the ``Target`` GPR attribute instead)
385403
* ``--RTS`` (use the ``Runtime`` GPR attribute instead)
386404

@@ -456,9 +474,9 @@ it was provided by the --rule-file option.
456474
An LKQL rule file can be any valid LKQL file, the only requirement is that it
457475
must export a ``rules`` top-level symbol. This symbol defines an object value
458476
containing rules configuration; keys are GNATcheck rules to enable; and values
459-
are objects containing the rule parameters. A rule parameter value can be of
460-
the boolean, the integer, the string, or the list of strings type, as shown in
461-
the simple example below:
477+
are list of objects, each one representing an instance of the rule with its
478+
parameters. A rule parameter value can be of the boolean, the integer, the
479+
string, or the list of strings type, as shown in the simple example below:
462480

463481
::
464482

@@ -467,6 +485,9 @@ the simple example below:
467485
Forbidden_Attributes: {Forbidden: ["GNAT"], Allowed: ["First", "Last"]}
468486
}
469487

488+
Using the "@" object notation is strongly advised to make your configuration
489+
file way more understandable:
490+
470491
Please read the :ref:`Predefined_Rules` documentation to view examples on how
471492
to provide parameters to rules through LKQL rule files.
472493

@@ -543,7 +564,7 @@ the following configuration will raise an error:
543564
::
544565

545566
val spark_rules = @{
546-
Warnings: "a"
567+
Warnings: {Arg: "a"}
547568
}
548569

549570
.. attention::
@@ -569,6 +590,62 @@ the following configuration will raise an error:
569590
Forbidden_Attributes: {Forbidden: ["Length"], instance_name: "Forbid_Attr"}
570591
}
571592

593+
You cannot provide more than **one** LKQL rule file when running GNATcheck. In
594+
order to compose a rule file with another you have to use the
595+
:ref:`LKQL importation mechanism<module_importation>` and concatenate rule
596+
objects. Here is an example of LKQL rule file composition:
597+
598+
.. code-block:: lkql
599+
600+
# common_rules.lkql
601+
602+
val rules = @{
603+
Goto_Statements
604+
}
605+
606+
.. code-block:: lkql
607+
608+
# specific_rules.lkql
609+
610+
import common_rules
611+
612+
val rules = common_rules.rules & @{
613+
Redundant_Null_Statements
614+
}
615+
616+
Then you can run GNATcheck with the ``specific_rules.lkql`` file as coding
617+
standard to perform rules defined in ``common_rules.lkql`` combined to the ones
618+
defined in ``specific_rules.lkql``.
619+
620+
.. note::
621+
622+
You can use the ``--lkql-path`` command-line switch and the
623+
``Check'Lkql_Path`` GPR attribute to configure directories LKQL rule files
624+
are going to be searched in.
625+
626+
You can enable the same rule in multiple files, but the constraint about the
627+
instance name uniqueness remains valid, meaning that such configuration is
628+
invalid:
629+
630+
.. code-block:: lkql
631+
632+
# common_rules.lkql
633+
634+
val rules = @{
635+
Forbidden_Attributes: {Forbidden: ["First"], instance_name: "Forbid_Attr"}
636+
}
637+
638+
.. code-block:: lkql
639+
640+
# specific_rules.lkql
641+
642+
import common_rules
643+
644+
val rules = common_rules.rules & @{
645+
Forbidden_Attributes: {Forbidden: ["Last"], instance_name: "Forbid_Attr"}
646+
}
647+
# error: This rule configuration defines two instances with the same name: "Forbid_Attr"
648+
572649
573650
.. _gnatcheck_Rule_Options:
574651

lkql_checker/src/gnatcheck-options.ads

+15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
1414
with GNAT.OS_Lib;
1515

1616
with Gnatcheck.Projects;
17+
with Gnatcheck.String_Utilities; use Gnatcheck.String_Utilities;
1718

1819
with GNATCOLL.Opt_Parse; use GNATCOLL.Opt_Parse;
1920

@@ -179,6 +180,10 @@ package Gnatcheck.Options is
179180
-- This variable should contain a full list of compilation options to be
180181
-- passed to gcc.
181182

183+
Additional_Lkql_Paths : String_Vector;
184+
-- Additional paths to add to the ``LKQL_PATH`` environment variable when
185+
-- spawning the LKQL worker.
186+
182187
Instance_Help_Emitted : Boolean := False;
183188
-- Whether the help message about the new instance system has already been
184189
-- emitted. This message should be removed in 26.0.
@@ -272,6 +277,16 @@ package Gnatcheck.Options is
272277
"specify the charset of the source files (default is "
273278
& "latin-1)");
274279

280+
package Lkql_Path is new
281+
Parse_Option_List
282+
(Parser => Parser,
283+
Long => "--lkql-path",
284+
Arg_Type => Unbounded_String,
285+
Accumulate => True,
286+
Help =>
287+
"specify directories to add to the 'LKQL_PATH' environment "
288+
& "variable when spawning the LKQL worker");
289+
275290
package Rules_Dirs is new
276291
Parse_Option_List
277292
(Parser => Parser,

lkql_checker/src/gnatcheck-projects.adb

+26
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ package body Gnatcheck.Projects is
7070
(GPR2."+" ("Check"), GPR2."+" ("Rules"));
7171
Rule_File_Attr : constant GPR2.Q_Attribute_Id :=
7272
(GPR2."+" ("Check"), GPR2."+" ("Rule_File"));
73+
Lkql_Path_Attr : constant GPR2.Q_Attribute_Id :=
74+
(GPR2."+" ("Check"), GPR2."+" ("Lkql_Path"));
7375
Default_Switches_Attr : constant GPR2.Q_Attribute_Id :=
7476
(GPR2."+" ("Check"), GPR2."+" ("Default_Switches"));
7577
Switches_Attr : constant GPR2.Q_Attribute_Id :=
@@ -274,6 +276,17 @@ package body Gnatcheck.Projects is
274276
end;
275277
end if;
276278

279+
-- Process the LKQL path
280+
if Proj.Has_Attribute (Lkql_Path_Attr) then
281+
List_Val := Load_List_Attribute (Lkql_Path_Attr);
282+
for Path of List_Val.all loop
283+
Additional_Lkql_Paths.Append
284+
(if Is_Absolute_Path (Path.all)
285+
then Path.all
286+
else Gnatcheck_Prj.Get_Project_Relative_File (Path.all));
287+
end loop;
288+
end if;
289+
277290
-- Process additional GNATcheck switches
278291
if Proj.Has_Attribute (Switches_Attr, Ada_Idx) then
279292
List_Val := Load_List_Attribute (Switches_Attr, Indexed => True);
@@ -753,6 +766,17 @@ package body Gnatcheck.Projects is
753766
(Rule_File_Attr,
754767
"Value is the name of an LKQL rule file to use when running "
755768
& "GNATcheck in this project.");
769+
Add
770+
(Lkql_Path_Attr,
771+
Index_Type => GPR2.Project.Registry.Attribute.No_Index,
772+
Value => List,
773+
Value_Case_Sensitive => True,
774+
Is_Allowed_In => Everywhere);
775+
GPR2.Project.Registry.Attribute.Description.Set_Attribute_Description
776+
(Lkql_Path_Attr,
777+
"Value is a list of directories to add to the LKQL_PATH environment "
778+
& "variable when GNATcheck is spawning the LKQL engine. This "
779+
& "variable is used to resolve module importations in LKQL sources.");
756780
Add
757781
(Switches_Attr,
758782
Index_Type => Language_Index,
@@ -1266,6 +1290,7 @@ package body Gnatcheck.Projects is
12661290
Disallow (Arg.Transitive_Closure.This, "-U" & In_Project_Msg);
12671291
Disallow (Arg.Scenario_Vars.This, "-Xname=val" & In_Project_Msg);
12681292
Disallow (Arg.Follow_Symbolic_Links.This, "-eL" & In_Project_Msg);
1293+
Disallow (Arg.Lkql_Path.This, "--lkql-path" & In_Project_Msg);
12691294
Disallow (Arg.Rules.This, "-r" & In_Project_Msg);
12701295
Disallow (Arg.Rule_File.This, "--rule-file" & In_Project_Msg);
12711296
Disallow (Arg.Target.This, "--target" & In_Project_Msg);
@@ -1291,6 +1316,7 @@ package body Gnatcheck.Projects is
12911316
Allow (Arg.Aggregate_Subproject.This);
12921317
Allow (Arg.Project_File.This);
12931318
Allow (Arg.Follow_Symbolic_Links.This);
1319+
Allow (Arg.Lkql_Path.This);
12941320
Allow (Arg.Rules.This);
12951321
Allow (Arg.Rule_File.This);
12961322
Allow (Arg.Target.This);

lkql_checker/src/gnatcheck_main.adb

+11
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ procedure Gnatcheck_Main is
129129

130130
Add_Path ("LD_LIBRARY_PATH", Lib);
131131
Add_Path ("LD_LIBRARY_PATH", Lib_LAL);
132+
133+
for Path of Additional_Lkql_Paths loop
134+
Add_Path ("LKQL_PATH", Path);
135+
end loop;
132136
end;
133137

134138
Free (Executable);
@@ -484,6 +488,13 @@ begin
484488
Add_Rule_By_Name (To_String (Rule), Prepend => True);
485489
end loop;
486490

491+
-- Add the command-line LKQL_PATH elements to the vector of additional
492+
-- searching paths.
493+
for Working_Dir_Path of Arg.Lkql_Path.Get loop
494+
Additional_Lkql_Paths.Append
495+
(Normalize_Pathname (To_String (Working_Dir_Path)));
496+
end loop;
497+
487498
-- Then analyze the command-line parameters
488499

489500
Gnatcheck_Prj.Scan_Arguments;

lkql_jit/cli/src/main/java/com/adacore/lkql_jit/cli/GNATCheckWorker.java

+35-73
Original file line numberDiff line numberDiff line change
@@ -399,50 +399,19 @@ private static void processInstancesObject(
399399

400400
// Check that the value associated to the rule name is an array like value
401401
if (args.hasArrayElements()) {
402-
// If there is no element in the argument list, just create an instance with no
403-
// argument and no alias.
404-
if (args.getArraySize() == 0) {
405-
if (toPopulate.containsKey(lowerRuleName)) {
406-
errorInLKQLRuleFile(
407-
lkqlRuleFile,
408-
"Multiple instances with the same name: " + lowerRuleName
409-
);
410-
} else {
411-
toPopulate.put(
412-
lowerRuleName,
413-
new RuleInstance(
414-
lowerRuleName,
415-
Optional.empty(),
416-
sourceMode,
417-
new HashMap<>()
418-
)
419-
);
420-
}
421-
}
422402
// Else iterate over each argument object and create one instance for each
423-
else {
424-
for (long i = 0; i < args.getArraySize(); i++) {
425-
processArgsObject(
426-
lkqlRuleFile,
427-
args.getArrayElement(i),
428-
sourceMode,
429-
lowerRuleName,
430-
toPopulate
431-
);
403+
for (long i = 0; i < args.getArraySize(); i++) {
404+
var arg = args.getArrayElement(i);
405+
if (arg.hasMembers()) {
406+
processArgsObject(lkqlRuleFile, arg, sourceMode, lowerRuleName, toPopulate);
407+
} else if (acceptSoleArgs(ruleName) && arg.isString()) {
408+
processSoleArg(arg, sourceMode, ruleName, toPopulate);
409+
} else {
410+
errorInLKQLRuleFile(lkqlRuleFile, "Arguments should be in an object value");
432411
}
433412
}
434-
} else if (args.hasMembers()) {
435-
processArgsObject(lkqlRuleFile, args, sourceMode, lowerRuleName, toPopulate);
436413
} else {
437-
// Allow sole arguments for some rules
438-
if (acceptSoleArgs(lowerRuleName) && args.isString()) {
439-
processSoleArg(args, sourceMode, lowerRuleName, toPopulate);
440-
} else {
441-
errorInLKQLRuleFile(
442-
lkqlRuleFile,
443-
"Rule arguments must be an object or indexable value"
444-
);
445-
}
414+
errorInLKQLRuleFile(lkqlRuleFile, "The value associated to a rule must be a list");
446415
}
447416
}
448417
}
@@ -455,43 +424,36 @@ private static void processArgsObject(
455424
final String ruleName,
456425
final Map<String, RuleInstance> toPopulate
457426
) throws LKQLRuleFileError {
458-
// Ensure that the given value has members (is an object)
459-
if (!argsObject.hasMembers()) {
460-
errorInLKQLRuleFile(lkqlRuleFile, "Arguments should be in an object value");
461-
}
462-
// If this is a valid value, process arguments in it
463-
else {
464-
// Compute the instance arguments and optional instance name
465-
String instanceId = ruleName;
466-
Optional<String> instanceName = Optional.empty();
467-
Map<String, String> arguments = new HashMap<>();
468-
for (String argName : argsObject.getMemberKeys()) {
469-
if (argName.equals("instance_name")) {
470-
String aliasName = argsObject.getMember("instance_name").asString();
471-
instanceId = aliasName.toLowerCase();
472-
instanceName = Optional.of(aliasName);
473-
} else {
474-
Value argValue = argsObject.getMember(argName);
475-
arguments.put(
476-
argName,
477-
argValue.isString() ? "\"" + argValue + "\"" : argValue.toString()
478-
);
479-
}
480-
}
481-
482-
// Add an instance in the instance map if it is not present
483-
if (toPopulate.containsKey(instanceId)) {
484-
errorInLKQLRuleFile(
485-
lkqlRuleFile,
486-
"Multiple instances with the same name: " + instanceId
487-
);
427+
// Compute the instance arguments and optional instance name
428+
String instanceId = ruleName;
429+
Optional<String> instanceName = Optional.empty();
430+
Map<String, String> arguments = new HashMap<>();
431+
for (String argName : argsObject.getMemberKeys()) {
432+
if (argName.equals("instance_name")) {
433+
String aliasName = argsObject.getMember("instance_name").asString();
434+
instanceId = aliasName.toLowerCase();
435+
instanceName = Optional.of(aliasName);
488436
} else {
489-
toPopulate.put(
490-
instanceId,
491-
new RuleInstance(ruleName, instanceName, sourceMode, arguments)
437+
Value argValue = argsObject.getMember(argName);
438+
arguments.put(
439+
argName,
440+
argValue.isString() ? "\"" + argValue + "\"" : argValue.toString()
492441
);
493442
}
494443
}
444+
445+
// Add an instance in the instance map if it is not present
446+
if (toPopulate.containsKey(instanceId)) {
447+
errorInLKQLRuleFile(
448+
lkqlRuleFile,
449+
"Multiple instances with the same name: " + instanceId
450+
);
451+
} else {
452+
toPopulate.put(
453+
instanceId,
454+
new RuleInstance(ruleName, instanceName, sourceMode, arguments)
455+
);
456+
}
495457
}
496458

497459
/** Internal function to process a sole string argument for a compiler-based rule. */

lkql_jit/language/src/main/java/com/adacore/lkql_jit/LKQLContext.java

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public final class LKQLContext {
5252
/** The call stack of the current language thread. */
5353
public final CallStack callStack = new CallStack();
5454

55+
/** The stack representing the current LKQL source chain. */
56+
public final Stack<Source> fromStack = new Stack<>();
57+
5558
// ----- Ada project attributes -----
5659

5760
/** The analysis context for the ada files. */

0 commit comments

Comments
 (0)