Skip to content

Commit 0d70ec9

Browse files
committed
GNATCheckWorker: unify duplicated rules when merging rules sets
This change improves how rules sets are merged by the worker by: * Improving diagnostics by giving precise instances locations. * Improving conflicts resolution when duplicate instances are merged together. In case the instances are the same (they share the same name and the same parameters), duplicates are simply ignored. In the case the instances run the same check (they have a different name but run the same check in the same configuration), a warning is emitted. Only the case where the instances share the same name but actually run different configurations of the check emits an error. * Producing more diagnostics when sets are merged, information-level diagnostics are emitted in verbose mode when an instance is added to the global instances set run by the driver, and, warning-level diagnostics are emitted when duplicate instances are added to the global set.
1 parent 97af142 commit 0d70ec9

File tree

33 files changed

+400
-30
lines changed

33 files changed

+400
-30
lines changed

lkql_checker/src/gnatcheck-compiler.adb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ package body Gnatcheck.Compiler is
600600
end if;
601601
end;
602602
end if;
603+
elsif Line_Len >= 16 and then Line (1 .. 13) = "WORKER_INFO: " then
604+
Info (Line (14 .. Line_Len));
603605
elsif Line_Len >= 16 and then Line (1 .. 16) = "WORKER_WARNING: " then
604606
Warning (Line (17 .. Line_Len));
605607
elsif Line_Len >= 14 and then Line (1 .. 14) = "WORKER_ERROR: " then
@@ -616,8 +618,6 @@ package body Gnatcheck.Compiler is
616618
-- the line if that's the case.
617619
if Line_Len = Line'Length then
618620
Skip_Line (File);
619-
else
620-
null;
621621
end if;
622622
else
623623
Analyze_Line (Line (1 .. Line_Len));
@@ -1862,6 +1862,11 @@ package body Gnatcheck.Compiler is
18621862
Num_Args := @ + 1;
18631863
Args (Num_Args) := new String'(LKQL_RF_Name);
18641864

1865+
if Verbose_Mode then
1866+
Num_Args := @ + 1;
1867+
Args (Num_Args) := new String'("--verbose");
1868+
end if;
1869+
18651870
-- Output the called command if in debug mode
18661871
if Arg.Debug_Mode.Get then
18671872
Put (Base_Name (Worker.all));

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ protected Map<String, RuleInstance> getRuleInstances() {
173173
ruleName,
174174
Optional.empty(),
175175
RuleInstance.SourceMode.GENERAL,
176-
instanceArgs.get(instanceId)
176+
instanceArgs.get(instanceId),
177+
null
177178
)
178179
);
179180
}

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

Lines changed: 154 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,10 @@ protected int executeScript(Context.Builder contextBuilder) {
201201
// If a LKQL rule config file has been provided, parse it and display the result
202202
if (this.args.lkqlConfigFile != null) {
203203
try {
204-
final var instances = parseLKQLRuleFile(this.args.lkqlConfigFile);
204+
final var instances = parseLKQLRuleFile(
205+
this.args.lkqlConfigFile,
206+
this.args.verbose
207+
);
205208
final var jsonInstances = new JSONObject(
206209
instances
207210
.entrySet()
@@ -245,7 +248,7 @@ protected int executeScript(Context.Builder contextBuilder) {
245248
for (var rulesFrom : this.args.rulesFroms) {
246249
if (!rulesFrom.isEmpty()) {
247250
try {
248-
instances.putAll(parseLKQLRuleFile(rulesFrom));
251+
instances.putAll(parseLKQLRuleFile(rulesFrom, this.args.verbose));
249252
} catch (LKQLRuleFileError e) {
250253
System.out.println(e.getMessage());
251254
return 0;
@@ -317,8 +320,10 @@ private static void errorInLKQLRuleFile(
317320
* @throws LKQLRuleFileError If there is any error in the provided LKQL rule file, preventing
318321
* the analysis to go further.
319322
*/
320-
private static Map<String, RuleInstance> parseLKQLRuleFile(final String lkqlRuleFileName)
321-
throws LKQLRuleFileError {
323+
private static Map<String, RuleInstance> parseLKQLRuleFile(
324+
final String lkqlRuleFileName,
325+
final boolean verbose
326+
) throws LKQLRuleFileError {
322327
final File lkqlFile = new File(lkqlRuleFileName);
323328
final String lkqlFileBasename = lkqlFile.getName();
324329
final Map<String, RuleInstance> res = new HashMap<>();
@@ -346,7 +351,8 @@ private static Map<String, RuleInstance> parseLKQLRuleFile(final String lkqlRule
346351
lkqlFileBasename,
347352
topLevel.getMember("rules"),
348353
RuleInstance.SourceMode.GENERAL,
349-
res
354+
res,
355+
verbose
350356
);
351357

352358
// Then get the optional Ada and SPARK instances
@@ -355,15 +361,17 @@ private static Map<String, RuleInstance> parseLKQLRuleFile(final String lkqlRule
355361
lkqlFileBasename,
356362
topLevel.getMember("ada_rules"),
357363
RuleInstance.SourceMode.ADA,
358-
res
364+
res,
365+
verbose
359366
);
360367
}
361368
if (topLevel.hasMember("spark_rules")) {
362369
processInstancesObject(
363370
lkqlFileBasename,
364371
topLevel.getMember("spark_rules"),
365372
RuleInstance.SourceMode.SPARK,
366-
res
373+
res,
374+
verbose
367375
);
368376
}
369377
} else {
@@ -390,7 +398,8 @@ private static void processInstancesObject(
390398
final String lkqlRuleFile,
391399
final Value instancesObject,
392400
final RuleInstance.SourceMode sourceMode,
393-
final Map<String, RuleInstance> toPopulate
401+
final Map<String, RuleInstance> toPopulate,
402+
final boolean verbose
394403
) throws LKQLRuleFileError {
395404
// Iterate on all instance object keys
396405
for (String ruleName : instancesObject.getMemberKeys()) {
@@ -399,13 +408,28 @@ private static void processInstancesObject(
399408

400409
// Check that the value associated to the rule name is an array like value
401410
if (args.hasArrayElements()) {
402-
// Else iterate over each argument object and create one instance for each
411+
// Then iterate over each argument object and create one instance for each
403412
for (long i = 0; i < args.getArraySize(); i++) {
404413
var arg = args.getArrayElement(i);
405414
if (arg.hasMembers()) {
406-
processArgsObject(lkqlRuleFile, arg, sourceMode, lowerRuleName, toPopulate);
415+
processArgsObject(
416+
lkqlRuleFile,
417+
arg,
418+
sourceMode,
419+
lowerRuleName,
420+
toPopulate,
421+
verbose
422+
);
407423
} else if (acceptSoleArgs(ruleName) && arg.isString()) {
408-
processSoleArg(arg, sourceMode, ruleName, toPopulate);
424+
processSoleArg(
425+
lkqlRuleFile,
426+
instancesObject,
427+
arg,
428+
sourceMode,
429+
ruleName,
430+
toPopulate,
431+
verbose
432+
);
409433
} else {
410434
errorInLKQLRuleFile(lkqlRuleFile, "Arguments should be in an object value");
411435
}
@@ -414,6 +438,39 @@ private static void processInstancesObject(
414438
errorInLKQLRuleFile(lkqlRuleFile, "The value associated to a rule must be a list");
415439
}
416440
}
441+
442+
// Post-process instances map and look for instances that are identical
443+
// but have different aliases.
444+
445+
// Build a new map to group all instances of the same rule into a list
446+
Map<String, List<RuleInstance>> rules = new HashMap();
447+
for (var instance : toPopulate.entrySet()) {
448+
var ruleName = instance.getValue().ruleName();
449+
var instancesList = rules.getOrDefault(ruleName, new ArrayList<>());
450+
instancesList.add(instance.getValue());
451+
rules.put(ruleName, instancesList);
452+
}
453+
454+
// Look for duplicates to warn the user about duplicate checks
455+
for (var rule : rules.entrySet()) {
456+
for (int i = 0; i < rule.getValue().size(); i++) {
457+
for (var instance : rule.getValue().subList(i + 1, rule.getValue().size())) {
458+
RuleInstance current = rule.getValue().get(i);
459+
if (current.isEquivalent(instance)) {
460+
System.err.println(
461+
"WORKER_WARNING: duplicate rules with different names, " +
462+
instance.instanceId() +
463+
" from " +
464+
instance.locationToGNATDiagnosisFormatString() +
465+
" runs the same check than rule " +
466+
current.instanceId() +
467+
" from " +
468+
current.locationToGNATDiagnosisFormatString()
469+
);
470+
}
471+
}
472+
}
473+
}
417474
}
418475

419476
/** Internal method to process an object value containing arguments for the given rule name. */
@@ -422,7 +479,8 @@ private static void processArgsObject(
422479
final Value argsObject,
423480
final RuleInstance.SourceMode sourceMode,
424481
final String ruleName,
425-
final Map<String, RuleInstance> toPopulate
482+
final Map<String, RuleInstance> toPopulate,
483+
final boolean verbose
426484
) throws LKQLRuleFileError {
427485
// Compute the instance arguments and optional instance name
428486
String instanceId = ruleName;
@@ -442,31 +500,103 @@ private static void processArgsObject(
442500
}
443501
}
444502

445-
// Add an instance in the instance map if it is not present
503+
RuleInstance newInstance = new RuleInstance(
504+
ruleName,
505+
instanceName,
506+
sourceMode,
507+
arguments,
508+
argsObject.getSourceLocation()
509+
);
510+
511+
// Add an instance in the instance map
446512
if (toPopulate.containsKey(instanceId)) {
447-
errorInLKQLRuleFile(
448-
lkqlRuleFile,
449-
"Multiple instances with the same name: " + instanceId
450-
);
513+
RuleInstance oldInstance = toPopulate.get(instanceId);
514+
// If the instance is already present, compare parameters, if they
515+
// are identical, skip the current instance and emit a warning.
516+
if (oldInstance.arguments().equals(newInstance.arguments())) {
517+
System.err.println(
518+
"WORKER_WARNING: duplicated rule: " +
519+
instanceId +
520+
" ignored from " +
521+
newInstance.locationToGNATDiagnosisFormatString() +
522+
", previous declaration at " +
523+
oldInstance.locationToGNATDiagnosisFormatString()
524+
);
525+
} else {
526+
// On the contrary, emit an error.
527+
errorInLKQLRuleFile(
528+
lkqlRuleFile,
529+
"multiple instances with the same name but different configuration: " +
530+
instanceId +
531+
" at " +
532+
newInstance.locationToGNATDiagnosisFormatString() +
533+
", previous declaration at " +
534+
oldInstance.locationToGNATDiagnosisFormatString() +
535+
" (instances should have unique names)"
536+
);
537+
}
451538
} else {
452-
toPopulate.put(
453-
instanceId,
454-
new RuleInstance(ruleName, instanceName, sourceMode, arguments)
455-
);
539+
if (verbose) {
540+
System.err.println(
541+
"WORKER_INFO: register new instance: " +
542+
instanceId +
543+
" from " +
544+
newInstance.locationToGNATDiagnosisFormatString()
545+
);
546+
}
547+
toPopulate.put(instanceId, newInstance);
456548
}
457549
}
458550

459551
/** Internal function to process a sole string argument for a compiler-based rule. */
460552
private static void processSoleArg(
553+
final String lkqlRuleFile,
554+
final Value instancesObject,
461555
final Value arg,
462556
final RuleInstance.SourceMode sourceMode,
463557
final String ruleName,
464-
final Map<String, RuleInstance> toPopulate
465-
) {
558+
final Map<String, RuleInstance> toPopulate,
559+
final boolean verbose
560+
) throws LKQLRuleFileError {
466561
// Create the new rule instance and add it to the "global" map
467562
Map<String, String> args = new HashMap<>();
468563
args.put("arg", "\"" + arg + "\"");
469-
toPopulate.put(ruleName, new RuleInstance(ruleName, Optional.empty(), sourceMode, args));
564+
RuleInstance newInstance = new RuleInstance(
565+
ruleName,
566+
Optional.empty(),
567+
sourceMode,
568+
args,
569+
// Since arg is a string literal, it doesn't have SourceSection
570+
// information yet, use the parent instancesObject's location
571+
// instead.
572+
instancesObject.getSourceLocation()
573+
);
574+
575+
if (!toPopulate.containsKey(ruleName)) {
576+
toPopulate.put(ruleName, newInstance);
577+
if (verbose) {
578+
System.err.println(
579+
"WORKER_INFO: register new instance: " +
580+
ruleName +
581+
" from " +
582+
newInstance.locationToGNATDiagnosisFormatString()
583+
);
584+
}
585+
} else {
586+
errorInLKQLRuleFile(
587+
lkqlRuleFile,
588+
"cannot add instance " +
589+
ruleName +
590+
" twice using the shortcut argument format from instances set included in rules set at: " +
591+
newInstance.locationToGNATDiagnosisFormatString() +
592+
". Previous instance has been declared in" +
593+
((newInstance
594+
.instanceLocation()
595+
.equals(toPopulate.get(ruleName).instanceLocation()))
596+
? " the same set"
597+
: ": " + toPopulate.get(ruleName).locationToGNATDiagnosisFormatString())
598+
);
599+
}
470600
}
471601

472602
/** Util function which returns whether a rule accepts sole argument. */

lkql_jit/options/src/main/java/com/adacore/lkql_jit/options/RuleInstance.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.util.Map;
99
import java.util.Optional;
10+
import org.graalvm.polyglot.SourceSection;
1011
import org.json.JSONException;
1112
import org.json.JSONObject;
1213

@@ -34,7 +35,8 @@ public record RuleInstance(
3435
String ruleName,
3536
Optional<String> instanceName,
3637
SourceMode sourceMode,
37-
Map<String, String> arguments
38+
Map<String, String> arguments,
39+
SourceSection instanceLocation
3840
) {
3941
// ----- Constructor -----
4042

@@ -57,7 +59,8 @@ public static RuleInstance fromJson(JSONObject jsonObject) throws JSONException
5759
jsonObject.getString("ruleName"),
5860
Optional.ofNullable(jsonObject.optString("instanceName", null)),
5961
SourceMode.valueOf(jsonObject.getString("sourceMode")),
60-
JSONUtils.parseStringMap(jsonObject.getJSONObject("arguments"))
62+
JSONUtils.parseStringMap(jsonObject.getJSONObject("arguments")),
63+
null
6164
);
6265
}
6366

@@ -77,6 +80,34 @@ public JSONObject toJson() {
7780
.put("instanceName", this.instanceName.orElse(null))
7881
.put("sourceMode", this.sourceMode.toString())
7982
.put("arguments", new JSONObject(this.arguments));
83+
// Do not output source location information for now
84+
}
85+
86+
/** Return whether this instance is equivalent to the other one, i.e.: it
87+
* runs the same checker with the same parameters (only the instance name
88+
* can differ). */
89+
public boolean isEquivalent(RuleInstance o) {
90+
return (
91+
this.ruleName.equals(o.ruleName) &&
92+
this.sourceMode.equals(o.sourceMode) &&
93+
this.arguments.equals(o.arguments)
94+
);
95+
}
96+
97+
/** Return a String holding the location of the instance in the GNAT
98+
* Diagnosis format when possible. */
99+
public String locationToGNATDiagnosisFormatString() {
100+
if (instanceLocation != null) {
101+
return (
102+
instanceLocation.getSource().getName() +
103+
":" +
104+
instanceLocation.getStartLine() +
105+
":" +
106+
instanceLocation.getStartColumn()
107+
);
108+
} else {
109+
return "undefined:0:0";
110+
}
80111
}
81112

82113
// ----- Override methods ------

0 commit comments

Comments
 (0)