@@ -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. */
0 commit comments