Repository: tlaplus/tlaplus
Pull request #1164: Add validation for `labelRoot` command line option
Author: login: kpadmasola
Labels: name: enhancement
description: Lets change things for the better
name: Tools
description: The command line tools - TLC, SANY, ...
name: PlusCal
description: PlusCal Algorithm Language
Created: 2025-03-25T08:55:38Z | Merged: 2025-03-29T19:43:57Z

+ Resolves issue #1092


Diff:
diff --git a/tlatools/org.lamport.tlatools/src/pcal/trans.java b/tlatools/org.lamport.tlatools/src/pcal/trans.java
index 5a404df0e3..238f6e02ac 100644
--- a/tlatools/org.lamport.tlatools/src/pcal/trans.java
+++ b/tlatools/org.lamport.tlatools/src/pcal/trans.java
@@ -12,6 +12,7 @@
 import java.util.List;
 import java.util.Vector;
 import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import pcal.ValidationCallBack.Generate;
 import pcal.exception.FileToStringVectorException;
@@ -295,6 +296,8 @@ class trans {
     private static final String TLA_TRANSLATION_COMMENT_LINE_PREFIX
     		= "\\* " + PcalParams.EndXlation1 + " " + PcalParams.EndXlation2;
     
+    private static final String LABEL_ROOT_REGEX = "[a-zA-Z][a-zA-Z0-9_]*";
+    private static final Pattern LABEL_ROOT_PATTERN = Pattern.compile(LABEL_ROOT_REGEX);
     
     /**
      * Main function called from the command line
@@ -1664,7 +1667,11 @@ static int parseAndProcessArguments(String[] args)
                 {
                     return CommandLineError("Label root must follow `-labelRoot' option");
                 }
-                PcalParams.LabelRoot = args[nextArg];
+                String labelRootArg = args[nextArg];
+                if (!LABEL_ROOT_PATTERN.matcher(labelRootArg).matches()) {
+                    return CommandLineError("Label root must be a valid PlusCal identifier (must start with a letter and contain only letters, numbers, and underscores)");
+                }
+                PcalParams.LabelRoot = labelRootArg;
             }
             // else if (option.equals("-readOnly") || (pcal && option.equals("readOnly"))) {
             // PcalParams.readOnly = true;
diff --git a/tlatools/org.lamport.tlatools/test/pcal/PcalParamsTest.java b/tlatools/org.lamport.tlatools/test/pcal/PcalParamsTest.java
index 6f158b99e2..c0f9961f3c 100644
--- a/tlatools/org.lamport.tlatools/test/pcal/PcalParamsTest.java
+++ b/tlatools/org.lamport.tlatools/test/pcal/PcalParamsTest.java
@@ -28,12 +28,65 @@
 import static org.junit.Assert.*;
 
 import org.junit.Test;
+import java.io.File;
+import java.io.IOException;
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
 
 public class PcalParamsTest {
 
+	private static File testFile;
+
+	@BeforeClass
+	public static void setUpClass() throws IOException {
+		// Create a test file in the system's temp directory
+		testFile = File.createTempFile("dummy", ".tla");
+		testFile.deleteOnExit(); // Ensure cleanup if JVM exits unexpectedly
+	}
+
+	@AfterClass
+	public static void tearDownClass() {
+		if (testFile != null && testFile.exists()) {
+			testFile.delete();
+		}
+	}
+
 	@Test
 	public void test() {
 		assertNotSame(PcalParams.version, "1.9");
 		assertTrue(PcalParams.versionWeight > PcalParams.VersionToNumber("1.9"));
 	}
+
+	@Test
+	public void testLabelRootValid() throws IOException {
+		String[] validCases = {
+			"validName",     // standard identifier with letters
+			"a1",           // letter followed by number
+			"A_1",          // letter followed by underscore and number
+			"abc123_456"    // complex mix of letters, numbers, and underscores
+		};
+
+		for (String valid : validCases) {
+			assertEquals("Valid identifier should be accepted: " + valid,
+				trans.STATUS_OK,
+				trans.parseAndProcessArguments(new String[] {"-labelRoot", valid, testFile.getAbsolutePath()}));
+		}
+	}
+
+	@Test
+	public void testLabelRootInvalid() throws IOException {
+		String[] invalidCases = {
+			"1abc",    // starts with number
+			"_abc",    // starts with underscore
+			"123",     // only numbers
+			"_",       // only underscore
+			""        // empty string
+		};
+
+		for (String invalid : invalidCases) {
+			int result = trans.parseAndProcessArguments(new String[] {"-labelRoot", invalid, testFile.getAbsolutePath()});
+			assertEquals("Invalid identifier should be rejected: " + invalid,
+				trans.STATUS_EXIT_WITH_ERRORS, result);
+		}
+	}
 }


Reviews:
user: login: ahelwer
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T14:10:47Z
user: login: ahelwer
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-27T14:11:30Z
user: login: lemmy
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-27T14:48:22Z
user: login: kpadmasola
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T13:05:45Z
user: login: lemmy
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-29T14:44:30Z

Review comments:
diff_hunk: @@ -295,7 +296,10 @@ class trans {
     private static final String TLA_TRANSLATION_COMMENT_LINE_PREFIX
     		= "\\* " + PcalParams.EndXlation1 + " " + PcalParams.EndXlation2;
     
+    private static final String LABEL_ROOT_REGEX = "[a-zA-Z][a-zA-Z0-9_]*";
+    private static final Pattern LABEL_ROOT_PATTERN = Pattern.compile(LABEL_ROOT_REGEX);
     
+
path: tlatools/org.lamport.tlatools/src/pcal/trans.java
user: login: ahelwer
body: nit: remove extra blank line
created_at: 2025-03-27T14:10:47Z
author_association: COLLABORATOR
diff_hunk: @@ -295,7 +296,10 @@ class trans {
     private static final String TLA_TRANSLATION_COMMENT_LINE_PREFIX
     		= "\\* " + PcalParams.EndXlation1 + " " + PcalParams.EndXlation2;
     
+    private static final String LABEL_ROOT_REGEX = "[a-zA-Z][a-zA-Z0-9_]*";
+    private static final Pattern LABEL_ROOT_PATTERN = Pattern.compile(LABEL_ROOT_REGEX);
     
+
path: tlatools/org.lamport.tlatools/src/pcal/trans.java
user: login: kpadmasola
body: done
created_at: 2025-03-29T13:05:45Z
author_association: CONTRIBUTOR

PR comments:
user: login: FedericoPonzi
created_at: 2025-03-25T09:18:26Z
body: Thanks a lot for your first contribution! Can you please add a test to validate your change? You can check on https://docs.tlapl.us/codebase:testing for some tips on how to run the tests locally.
author_association: COLLABORATOR
user: login: kpadmasola
created_at: 2025-03-25T10:47:56Z
body: @FedericoPonzi The tests are failing for me locally. I don't think it is an issue related to my changes, more likely something related to the environment. I will need to debug further to identify the root cause. Please let me know if you have any tips for debugging this issue. Thank you.

```
$ ant -verbose -debug -f customBuild.xml test -Dtest.halt=true
...
BUILD FAILED
/tmp/tlaplus/tlatools/org.lamport.tlatools/customBuild.xml:476: Test tlc2.tool.distributed.DieHardDistributedTLCTest failed
	at org.apache.tools.ant.taskdefs.optional.junit.JUnitTask.actOnTestResult(JUnitTask.java:2216)
	at org.apache.tools.ant.taskdefs.optional.junit.JUnitTask.execute(JUnitTask.java:1042)
	at org.apache.tools.ant.taskdefs.optional.junit.JUnitTask.executeOrQueue(JUnitTask.java:2163)
	at org.apache.tools.ant.taskdefs.optional.junit.JUnitTask.execute(JUnitTask.java:854)
	at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:299)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:99)
	at org.apache.tools.ant.Task.perform(Task.java:350)
	at org.apache.tools.ant.Target.execute(Target.java:449)
	at org.apache.tools.ant.Target.performTasks(Target.java:470)
	at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1401)
	at org.apache.tools.ant.Project.executeTarget(Project.java:1374)
	at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
	at org.apache.tools.ant.Project.executeTargets(Project.java:1264)
	at org.apache.tools.ant.Main.runBuild(Main.java:818)
	at org.apache.tools.ant.Main.startAnt(Main.java:223)
	at org.apache.tools.ant.launch.Launcher.run(Launcher.java:284)
	at org.apache.tools.ant.launch.Launcher.main(Launcher.java:101)
author_association: CONTRIBUTOR
user: login: FedericoPonzi
created_at: 2025-03-25T10:55:28Z
body: Can you try with:
```
ant -f customBuild.xml compile compile-test test-set -Dtest.testcases="<path to your test inside test folder>"
```
I think you'll need to add `compile` to compile the codebase, `compile-test` to compile the test codebase and then you can use test-set to run only the test you're adding rather than the whole suite. If you don't have a test yet, you can test the local setup is working by running:
```
ant -f customBuild.xml compile compile-test shorttests
```

author_association: COLLABORATOR
user: login: kpadmasola
created_at: 2025-03-25T12:02:31Z
body: @FedericoPonzi Thanks, but no luck :( All the tests failed due to Errors.
author_association: CONTRIBUTOR
user: login: FedericoPonzi
created_at: 2025-03-25T12:20:55Z
body: Can you share the result of:
```
ant -f customBuild.xml compile compile-test
```
also, what's the output of `javac --version`?
author_association: COLLABORATOR
user: login: kpadmasola
created_at: 2025-03-25T15:48:50Z
body: ```shell
$ javac -version
javac 11.0.2
```
result of `ant -f customBuild.xml compile compile-test`
```
$ ant -f customBuild.xml compile compile-test

Buildfile: /tmp/tlaplus/tlatools/org.lamport.tlatools/customBuild.xml

clean:

generate:
     [java] Java Compiler Compiler Version 4.0 (Parser Generator)
     [java] (type "javacc" with no arguments for help)
     [java] Reading from file javacc/tla+.jj . . .
     [java] Warning: Choice conflict involving two expansions at
     [java]          line 2784, column 7 and line 2802, column 7 respectively.
     [java]          A common prefix is: "CASE"
     [java]          Consider using a lookahead of 2 for earlier expansion.
     [java] Warning: Line 1312, Column 3: Non-ASCII characters used in regular expression.

     [java] Please make sure you use the correct Reader when you create the parser that can handle your character set.
     [java] Parser generated with 0 errors and 5 warnings.
     [java] Warning:  "\u2208" cannot be matched as a string literal token at line 1261, column 33. It will be matched as  <IN>.
     [java] Warning:  "\u2227" cannot be matched as a string literal token at line 1262, column 28. It will be matched as  <AND>.
     [java] Warning:  "\u2228" cannot be matched as a string literal token at line 1267, column 28. It will be matched as  <OR>.
     [java] Java Compiler Compiler Version 4.0 (Documentation Generator Version 0.1.4)
     [java] (type "jjdoc" with no arguments for help)
     [java] Reading from file javacc/tla+.jj . . .
     [java] Grammar documentation generated successfully in javacc/tla+.html

compile:
     [echo] 
     [echo] ==================================================================
     [echo] = This code uses sun.misc.Unsafe for manual memory management in =
     [echo] = the class src/tlc2/tool/fp/LongArray.java. This unsafe API is  =
     [echo] = marked as internal meaning it can change without warning. Java =
     [echo] = emits a lot of warnings about this and makes it essentially    =
     [echo] = impossible to silence them, so just ignore them. The relevant  =
     [echo] = unsafe APIs have been standardized in Java 22, and we will     =
     [echo] = switch to them as soon as a long-term support release occurs.  =
     [echo] ==================================================================
     [echo] 		
    [javac] Compiling 578 source files to /tmp/tlaplus/tlatools/org.lamport.tlatools/class
    [javac] /tmp/tlaplus/tlatools/org.lamport.tlatools/src/tla2sany/parser/SimpleCharStream.java:206: warning: [dep-ann] deprecated item is not annotated with @Deprecated
    [javac]   public int getColumn() {
    [javac]              ^
    [javac] /tmp/tlaplus/tlatools/org.lamport.tlatools/src/tla2sany/parser/SimpleCharStream.java:215: warning: [dep-ann] deprecated item is not annotated with @Deprecated
    [javac]   public int getLine() {
    [javac]              ^
    [javac] /tmp/tlaplus/tlatools/org.lamport.tlatools/src/tlc2/tool/fp/LongArray.java:47: warning: Unsafe is internal proprietary API and may be removed in a future release
    [javac] 	private final sun.misc.Unsafe unsafe;
    [javac] 	                      ^
    [javac] /tmp/tlaplus/tlatools/org.lamport.tlatools/src/tlc2/tool/fp/LongArray.java:113: warning: Unsafe is internal proprietary API and may be removed in a future release
    [javac] 	private static sun.misc.Unsafe getUnsafe() {
    [javac] 	                       ^
    [javac] /tmp/tlaplus/tlatools/org.lamport.tlatools/src/tlc2/tool/fp/LongArray.java:117: warning: Unsafe is internal proprietary API and may be removed in a future release
    [javac] 			final Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
    [javac] 			                        ^
    [javac] /tmp/tlaplus/tlatools/org.lamport.tlatools/src/tlc2/tool/fp/LongArray.java:119: warning: Unsafe is internal proprietary API and may be removed in a future release
    [javac] 			return (sun.misc.Unsafe) f.get(null);
    [javac] 			                ^
    [javac] Note: Some input files use or override a deprecated API.
    [javac] Note: Recompile with -Xlint:deprecation for details.
    [javac] Note: Some input files use unchecked or unsafe operations.
    [javac] Note: Recompile with -Xlint:unchecked for details.
    [javac] 6 warnings

compile-test:
    [javac] Compiling 681 source files to /tmp/tlaplus/tlatools/org.lamport.tlatools/test-class
     [copy] Copying 3 files to /tmp/tlaplus/tlatools/org.lamport.tlatools/test-class
     [copy] Copying 1635 files to /tmp/tlaplus/tlatools/org.lamport.tlatools/TLA+ Tools

BUILD SUCCESSFUL
Total time: 31 seconds
```

author_association: CONTRIBUTOR
user: login: kpadmasola
created_at: 2025-03-29T13:05:03Z
body: @FedericoPonzi Finally I got the tests working using Eclipse. I added a couple of testcases now and addressed review comments. Thanks.
author_association: CONTRIBUTOR