Initial support for maximumremovedandroidloglevel directive

This adds support for `-maximumremovedandroidloglevel <int>`. The syntax `-maximumremovedandroidloglevel <int> <class_specification>`, which can be used to set the maximum-removed-android-log-level per method, is still not supported.

Example: The value of android.util.log.INFO is 4. Therefore, specifying `-maximumremovedandroidloglevel 4` will remove all calls to Log.v(), Log.d(), and Log.i(), as well as it will replace calls to Log.isLoggable(X, {2, 3, 4}) by false.

Bug: 147870129
Change-Id: I8bf9ecd562c22a99c7aa53031857300829f77abe
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index 9c5a8d1..91e952a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -32,6 +32,10 @@
   public LibraryMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
     this.appView = appView;
     register(new BooleanMethodOptimizer(appView));
+
+    if (LogMethodOptimizer.isEnabled(appView)) {
+      register(new LogMethodOptimizer(appView));
+    }
   }
 
   private void register(LibraryMethodModelCollection optimizer) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
new file mode 100644
index 0000000..f65b57a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.library;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class LogMethodOptimizer implements LibraryMethodModelCollection {
+
+  private static final int VERBOSE = 2;
+  private static final int DEBUG = 3;
+  private static final int INFO = 4;
+  private static final int WARN = 5;
+  private static final int ERROR = 6;
+  private static final int ASSERT = 7;
+
+  private final AppView<? extends AppInfoWithSubtyping> appView;
+
+  private final DexType logType;
+
+  private final DexMethod isLoggableMethod;
+  private final DexMethod vMethod;
+  private final DexMethod dMethod;
+  private final DexMethod iMethod;
+  private final DexMethod wMethod;
+  private final DexMethod eMethod;
+  private final DexMethod wtfMethod;
+
+  LogMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+    this.appView = appView;
+
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexType logType = dexItemFactory.createType("Landroid/util/Log;");
+    this.logType = logType;
+    this.isLoggableMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.booleanType, dexItemFactory.stringType, dexItemFactory.intType),
+            "isLoggable");
+    this.vMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "v");
+    this.dMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "d");
+    this.iMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "i");
+    this.wMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "w");
+    this.eMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "e");
+    this.wtfMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "wtf");
+  }
+
+  public static boolean isEnabled(AppView<?> appView) {
+    return appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel() >= VERBOSE;
+  }
+
+  @Override
+  public DexType getType() {
+    return logType;
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {
+    int maxRemovedAndroidLogLevel =
+        appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel();
+    if (singleTarget.method == isLoggableMethod) {
+      Value logLevelValue = invoke.arguments().get(1).getAliasedValue();
+      if (!logLevelValue.isPhi() && !logLevelValue.hasLocalInfo()) {
+        Instruction definition = logLevelValue.definition;
+        if (definition.isConstNumber()) {
+          int logLevel = definition.asConstNumber().getIntValue();
+          replaceInvokeWithConstNumber(
+              code, instructionIterator, invoke, maxRemovedAndroidLogLevel >= logLevel ? 0 : 1);
+        }
+      }
+    } else if (singleTarget.method == vMethod) {
+      if (maxRemovedAndroidLogLevel >= VERBOSE) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == dMethod) {
+      if (maxRemovedAndroidLogLevel >= DEBUG) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == iMethod) {
+      if (maxRemovedAndroidLogLevel >= INFO) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == wMethod) {
+      if (maxRemovedAndroidLogLevel >= WARN) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == eMethod) {
+      if (maxRemovedAndroidLogLevel >= ERROR) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == wtfMethod) {
+      if (maxRemovedAndroidLogLevel >= ASSERT) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    }
+  }
+
+  private void replaceInvokeWithConstNumber(
+      IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, int value) {
+    if (invoke.hasOutValue() && invoke.outValue().hasAnyUsers()) {
+      instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+    } else {
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index bfad990..3c91bc8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -70,6 +70,7 @@
     private boolean keepRuleSynthesisForRecompilation = false;
     private boolean configurationDebugging = false;
     private boolean dontUseMixedCaseClassnames = false;
+    private int maxRemovedAndroidLogLevel = 1;
 
     private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
       this.dexItemFactory = dexItemFactory;
@@ -288,6 +289,14 @@
       this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
     }
 
+    public int getMaxRemovedAndroidLogLevel() {
+      return maxRemovedAndroidLogLevel;
+    }
+
+    public void setMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
+      this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+    }
+
     /**
      * This synthesizes a set of keep rules that are necessary in order to be able to successfully
      * recompile the generated dex files with the same keep rules.
@@ -346,7 +355,8 @@
               adaptResourceFileContents.build(),
               keepDirectories.build(),
               configurationDebugging,
-              dontUseMixedCaseClassnames);
+              dontUseMixedCaseClassnames,
+              maxRemovedAndroidLogLevel);
 
       reporter.failIfPendingErrors();
 
@@ -415,6 +425,7 @@
   private final ProguardPathFilter keepDirectories;
   private final boolean configurationDebugging;
   private final boolean dontUseMixedCaseClassnames;
+  private final int maxRemovedAndroidLogLevel;
 
   private ProguardConfiguration(
       String parsedConfiguration,
@@ -454,7 +465,8 @@
       ProguardPathFilter adaptResourceFileContents,
       ProguardPathFilter keepDirectories,
       boolean configurationDebugging,
-      boolean dontUseMixedCaseClassnames) {
+      boolean dontUseMixedCaseClassnames,
+      int maxRemovedAndroidLogLevel) {
     this.parsedConfiguration = parsedConfiguration;
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
@@ -493,6 +505,7 @@
     this.keepDirectories = keepDirectories;
     this.configurationDebugging = configurationDebugging;
     this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
+    this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
   }
 
   /**
@@ -659,6 +672,10 @@
     return dontUseMixedCaseClassnames;
   }
 
+  public int getMaxRemovedAndroidLogLevel() {
+    return maxRemovedAndroidLogLevel;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index a589f9a..cc05e23 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -58,7 +58,7 @@
       "maximuminlinedcodelength");
 
   private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS =
-      ImmutableList.of("runtype", "laststageoutput", "maximumremovedandroidloglevel");
+      ImmutableList.of("runtype", "laststageoutput");
 
   private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
       "forceprocessing",
@@ -398,6 +398,14 @@
         configurationBuilder.setConfigurationDebugging(true);
       } else if (acceptString("dontusemixedcaseclassnames")) {
         configurationBuilder.setDontUseMixedCaseClassnames(true);
+      } else if (acceptString("maximumremovedandroidloglevel")) {
+        skipWhitespace();
+        Integer maxRemovedAndroidLogLevel = acceptInteger();
+        if (maxRemovedAndroidLogLevel != null) {
+          configurationBuilder.setMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
+        } else {
+          throw parseError("Expected integer", getPosition());
+        }
       } else {
         String unknownOption = acceptString();
         String devMessage = "";
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index e20e882..12c9268 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -17,11 +18,13 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -62,6 +65,13 @@
     return new TestApplication(appView, method, additionalCode);
   }
 
+  private static InternalOptions createOptions() {
+    Reporter reporter = new Reporter();
+    ProguardConfiguration proguardConfiguration =
+        ProguardConfiguration.builder(new DexItemFactory(), reporter).build();
+    return new InternalOptions(proguardConfiguration, reporter);
+  }
+
   private TestApplication codeForMethodReplaceTest(int a, int b) throws ExecutionException {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
@@ -108,7 +118,7 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -190,7 +200,7 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -267,7 +277,7 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -400,7 +410,7 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -513,7 +523,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -624,7 +634,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -737,7 +747,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -893,7 +903,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -1139,7 +1149,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
new file mode 100644
index 0000000..1314274
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
@@ -0,0 +1,195 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.logging;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AndroidLogRemovalTest extends TestBase {
+
+  private final int maxRemovedAndroidLogLevel;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, log level: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(1, 2, 3, 4, 5, 6, 7),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public AndroidLogRemovalTest(int maxRemovedAndroidLogLevel, TestParameters parameters) {
+    this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path libraryFile =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(
+                transformer(Log.class).setClassDescriptor("Landroid/util/Log;").transform())
+            .addKeepAllClassesRule()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(TestClass.class)
+                .transformMethodInsnInMethod(
+                    "main",
+                    (opcode, owner, name, descriptor, isInterface, continuation) ->
+                        continuation.apply(
+                            opcode,
+                            owner.endsWith("$Log") ? "android/util/Log" : owner,
+                            name,
+                            descriptor,
+                            isInterface))
+                .transform())
+        .addLibraryFiles(libraryFile, runtimeJar(parameters.getBackend()))
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-maximumremovedandroidloglevel " + maxRemovedAndroidLogLevel)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(libraryFile)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedOutputForMaxLogLevel());
+  }
+
+  private String getExpectedOutputForMaxLogLevel() {
+    switch (maxRemovedAndroidLogLevel) {
+      case 1:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] VERBOSE."), 2),
+            StringUtils.times(StringUtils.lines("[R8] DEBUG."), 2),
+            StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+            StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 2:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] DEBUG."), 2),
+            StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+            StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 3:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+            StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 4:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 5:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 6:
+        return StringUtils.join("", StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 7:
+        return "";
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Log.v("R8", "VERBOSE.");
+      if (Log.isLoggable("R8", Log.VERBOSE)) {
+        System.out.println("[R8] VERBOSE.");
+      }
+
+      Log.d("R8", "DEBUG.");
+      if (Log.isLoggable("R8", Log.DEBUG)) {
+        System.out.println("[R8] DEBUG.");
+      }
+
+      Log.i("R8", "INFO.");
+      if (Log.isLoggable("R8", Log.INFO)) {
+        System.out.println("[R8] INFO.");
+      }
+
+      Log.w("R8", "WARN.");
+      if (Log.isLoggable("R8", Log.WARN)) {
+        System.out.println("[R8] WARN.");
+      }
+
+      Log.e("R8", "ERROR.");
+      if (Log.isLoggable("R8", Log.ERROR)) {
+        System.out.println("[R8] ERROR.");
+      }
+
+      Log.wtf("R8", "ASSERT.");
+      if (Log.isLoggable("R8", Log.ASSERT)) {
+        System.out.println("[R8] ASSERT.");
+      }
+    }
+  }
+
+  public static class Log {
+
+    public static final int VERBOSE = 2;
+    public static final int DEBUG = 3;
+    public static final int INFO = 4;
+    public static final int WARN = 5;
+    public static final int ERROR = 6;
+    public static final int ASSERT = 7;
+
+    public static boolean isLoggable(String tag, int level) {
+      return true;
+    }
+
+    public static int v(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int d(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int i(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int w(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int e(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int wtf(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+  }
+}