Introduce flag --incompatible_disallow_old_octal_notation

https://github.com/bazelbuild/bazel/issues/8059

When the flag is enabled, forbid octal numbers like `0123`; they should be written `0o123` instead.

RELNOTES: Flag `--incompatible_disallow_old_octal_notation` is added. See //github.com/bazelbuild/bazel/issues/8059
PiperOrigin-RevId: 243830436
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
index 916fb69..3bb2b55 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
@@ -332,6 +332,21 @@
               + "instead return a list of provider instances.")
   public boolean incompatibleDisallowStructProviderSyntax;
 
+  @Option(
+      name = "incompatible_disallow_old_octal_notation",
+      defaultValue = "false",
+      category = "incompatible changes",
+      documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
+      effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS},
+      metadataTags = {
+        OptionMetadataTag.INCOMPATIBLE_CHANGE,
+        OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+      },
+      help =
+          "If set to true, octal numbers like `0123` are forbidden, they should be written "
+              + "`0o123` instead. See https://github.com/bazelbuild/bazel/issues/8059")
+  public boolean incompatibleDisallowOldOctalNotation;
+
   /** Controls legacy arguments to ctx.actions.Args#add. */
   @Option(
       name = "incompatible_disallow_old_style_args_add",
@@ -564,6 +579,7 @@
         .incompatibleDisallowLoadLabelsToCrossPackageBoundaries(
             incompatibleDisallowLoadLabelsToCrossPackageBoundaries)
         .incompatibleDisallowNativeInBuildFile(incompatibleDisallowNativeInBuildFile)
+        .incompatibleDisallowOldOctalNotation(incompatibleDisallowOldOctalNotation)
         .incompatibleDisallowOldStyleArgsAdd(incompatibleDisallowOldStyleArgsAdd)
         .incompatibleDisallowStructProviderSyntax(incompatibleDisallowStructProviderSyntax)
         .incompatibleExpandDirectories(incompatibleExpandDirectories)
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
index da2ee67..fe334f1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
@@ -541,6 +541,7 @@
   public static void execAndExport(BuildFileAST ast, Label extensionLabel,
       EventHandler eventHandler,
       com.google.devtools.build.lib.syntax.Environment extensionEnv) throws InterruptedException {
+    ast.replayLexerEvents(extensionEnv, eventHandler);
     ImmutableList<Statement> statements = ast.getStatements();
     for (Statement statement : statements) {
       ast.execTopLevelStatement(statement, extensionEnv, eventHandler);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java b/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
index 13c81ae..47beaf1 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
@@ -49,6 +49,8 @@
    */
   private final boolean containsErrors;
 
+  private final List<Event> octalEvents;
+
   @Nullable private final String contentHashCode;
 
   private BuildFileAST(
@@ -57,13 +59,15 @@
       String contentHashCode,
       Location location,
       ImmutableList<Comment> comments,
-      @Nullable ImmutableList<SkylarkImport> imports) {
+      @Nullable ImmutableList<SkylarkImport> imports,
+      List<Event> octalEvents) {
     this.statements = statements;
     this.containsErrors = containsErrors;
     this.contentHashCode = contentHashCode;
     this.comments = comments;
     this.setLocation(location);
     this.imports = imports;
+    this.octalEvents = octalEvents;
   }
 
   private static BuildFileAST create(
@@ -98,7 +102,8 @@
         contentHashCode,
         result.location,
         ImmutableList.copyOf(result.comments),
-        skylarkImports.second);
+        skylarkImports.second,
+        result.octalEvents);
   }
 
   private static BuildFileAST create(
@@ -135,7 +140,8 @@
         null,
         this.statements.get(firstStatement).getLocation(),
         ImmutableList.of(),
-        imports.build());
+        imports.build(),
+        octalEvents);
   }
 
   /**
@@ -201,6 +207,16 @@
     }
     return imports.build();
   }
+
+  /** Returns true if there was no error event. */
+  public boolean replayLexerEvents(Environment env, EventHandler eventHandler) {
+    if (env.getSemantics().incompatibleDisallowOldOctalNotation() && !octalEvents.isEmpty()) {
+      Event.replayEventsOn(eventHandler, octalEvents);
+      return false;
+    }
+    return true;
+  }
+
   /**
    * Executes this build file in a given Environment.
    *
@@ -219,6 +235,9 @@
    */
   public boolean exec(Environment env, EventHandler eventHandler) throws InterruptedException {
     boolean ok = true;
+    if (!replayLexerEvents(env, eventHandler)) {
+      return false;
+    }
     for (Statement stmt : statements) {
       if (!execTopLevelStatement(stmt, env, eventHandler)) {
         ok = false;
@@ -367,10 +386,11 @@
             .addAll(result.statements)
             .build(),
         result.containsErrors,
-        /* contentHashCode= */null,
+        /* contentHashCode= */ null,
         result.location,
         ImmutableList.copyOf(result.comments),
-        /* imports= */null);
+        /* imports= */ null,
+        result.octalEvents);
   }
 
   /**
@@ -383,7 +403,8 @@
     if (valid || containsErrors) {
       return this;
     }
-    return new BuildFileAST(statements, true, contentHashCode, getLocation(), comments, imports);
+    return new BuildFileAST(
+        statements, true, contentHashCode, getLocation(), comments, imports, octalEvents);
   }
 
   public static BuildFileAST parseString(EventHandler eventHandler, String... content) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
index 5507053..ea93e00 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
@@ -104,6 +104,14 @@
   private int dents; // number of saved INDENT (>0) or OUTDENT (<0) tokens to return
 
   /**
+   * OctalEvents contains the errors related to the old octal notation (0123 instead of 0o123). This
+   * is not handled by the normal eventHandler. Instead, it is passed to the parser and then the
+   * AST. During the evaluation, we can decide to show the events based on a flag in
+   * StarlarkSemantics. This code is temporary, during the migration.
+   */
+  private List<Event> octalEvents = new ArrayList<>();
+
+  /**
    * Constructs a lexer which tokenizes the contents of the specified InputBuffer. Any errors during
    * lexing are reported on "handler".
    */
@@ -129,6 +137,10 @@
     return comments;
   }
 
+  List<Event> getOctalEvents() {
+    return octalEvents;
+  }
+
   /**
    * Returns the filename from which the lexer's input came. Returns an empty value if the input
    * came from a string.
@@ -689,6 +701,10 @@
     } else if (literal.startsWith("0") && literal.length() > 1) {
       radix = 8;
       substring = literal.substring(1);
+      octalEvents.add(
+          Event.error(
+              createLocation(oldPos, pos),
+              "Invalid octal value `" + literal + "`, should be: `0o" + substring + "`."));
     } else {
       radix = 10;
       substring = literal;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index 81f34c3..cec994e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -62,14 +62,21 @@
     /** Whether the file contained any errors. */
     public final boolean containsErrors;
 
-    public ParseResult(List<Statement> statements, List<Comment> comments, Location location,
-        boolean containsErrors) {
+    public final List<Event> octalEvents;
+
+    public ParseResult(
+        List<Statement> statements,
+        List<Comment> comments,
+        Location location,
+        boolean containsErrors,
+        List<Event> octalEvents) {
       // No need to copy here; when the object is created, the parser instance is just about to go
       // out of scope and be garbage collected.
       this.statements = Preconditions.checkNotNull(statements);
       this.comments = Preconditions.checkNotNull(comments);
       this.location = location;
       this.containsErrors = containsErrors;
+      this.octalEvents = octalEvents;
     }
   }
 
@@ -202,7 +209,11 @@
     }
     boolean errors = parser.errorsCount > 0 || lexer.containsErrors();
     return new ParseResult(
-        statements, lexer.getComments(), locationFromStatements(lexer, statements), errors);
+        statements,
+        lexer.getComments(),
+        locationFromStatements(lexer, statements),
+        errors,
+        lexer.getOctalEvents());
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
index e07179e..aaddb98 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
@@ -151,6 +151,8 @@
 
   public abstract boolean incompatibleDisallowNativeInBuildFile();
 
+  public abstract boolean incompatibleDisallowOldOctalNotation();
+
   public abstract boolean incompatibleDisallowOldStyleArgsAdd();
 
   public abstract boolean incompatibleDisallowStructProviderSyntax();
@@ -219,6 +221,7 @@
           .incompatibleDisallowLegacyJavaInfo(false)
           .incompatibleDisallowLoadLabelsToCrossPackageBoundaries(true)
           .incompatibleDisallowNativeInBuildFile(false)
+          .incompatibleDisallowOldOctalNotation(false)
           .incompatibleDisallowOldStyleArgsAdd(true)
           .incompatibleDisallowStructProviderSyntax(false)
           .incompatibleExpandDirectories(true)
@@ -279,6 +282,8 @@
 
     public abstract Builder incompatibleDisallowLoadLabelsToCrossPackageBoundaries(boolean value);
 
+    public abstract Builder incompatibleDisallowOldOctalNotation(boolean value);
+
     public abstract Builder incompatibleDisallowOldStyleArgsAdd(boolean value);
 
     public abstract Builder incompatibleDisallowNativeInBuildFile(boolean value);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index 5b08481..294d329 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -143,6 +143,7 @@
         "--incompatible_disallow_legacy_java_provider=" + rand.nextBoolean(),
         "--incompatible_disallow_load_labels_to_cross_package_boundaries=" + rand.nextBoolean(),
         "--incompatible_disallow_native_in_build_file=" + rand.nextBoolean(),
+        "--incompatible_disallow_old_octal_notation=" + rand.nextBoolean(),
         "--incompatible_disallow_old_style_args_add=" + rand.nextBoolean(),
         "--incompatible_disallow_struct_provider_syntax=" + rand.nextBoolean(),
         "--incompatible_expand_directories=" + rand.nextBoolean(),
@@ -190,6 +191,7 @@
         .incompatibleDisallowLegacyJavaProvider(rand.nextBoolean())
         .incompatibleDisallowLoadLabelsToCrossPackageBoundaries(rand.nextBoolean())
         .incompatibleDisallowNativeInBuildFile(rand.nextBoolean())
+        .incompatibleDisallowOldOctalNotation(rand.nextBoolean())
         .incompatibleDisallowOldStyleArgsAdd(rand.nextBoolean())
         .incompatibleDisallowStructProviderSyntax(rand.nextBoolean())
         .incompatibleExpandDirectories(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
index ab46118..43b5c4b 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
@@ -2964,4 +2964,28 @@
       }
     }
   }
+
+  @Test
+  public void testOldOctalNotationIsForbidden() throws Exception {
+    setSkylarkSemanticsOptions("--incompatible_disallow_old_octal_notation=true");
+
+    scratch.file("test/extension.bzl", "y = 0246");
+
+    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//test:r");
+    assertContainsEvent("Invalid octal value `0246`, should be: `0o246`");
+  }
+
+  @Test
+  public void testOldOctalNotation() throws Exception {
+    setSkylarkSemanticsOptions("--incompatible_disallow_old_octal_notation=false");
+
+    scratch.file("test/extension.bzl", "y = 0246");
+
+    scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+    getConfiguredTarget("//test:r");
+  }
 }