Merge "Mark obsolete when DexEncodedMethod instance became obsolete."
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index c29453a..f594c71 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -76,7 +76,13 @@
   }
 
   public DexEncodedMethod definitionFor(DexMethod method) {
-    return (DexEncodedMethod) getDefinitions(method.getHolder()).get(method);
+    DexType holderType = method.getHolder();
+    DexEncodedMethod cached = (DexEncodedMethod) getDefinitions(holderType).get(method);
+    if (cached != null && cached.isObsolete()) {
+      definitions.remove(holderType);
+      cached = (DexEncodedMethod) getDefinitions(holderType).get(method);
+    }
+    return cached;
   }
 
   public DexEncodedField definitionFor(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 248765d..5af5739 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -104,6 +104,28 @@
   private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
   private int classFileVersion = -1;
 
+  // This flag indicates the current instance is no longer up-to-date as another instance was
+  // created based on this. Any further (public) operations on this instance will raise an error
+  // to catch potential bugs due to the inconsistency (e.g., http://b/111893131)
+  // Any newly added `public` method should check if `this` instance is obsolete.
+  private boolean obsolete = false;
+
+  private void checkIfObsolete() {
+    assert !obsolete;
+  }
+
+  public boolean isObsolete() {
+    // Do not be cheating. This util can be used only if you're going to do appropriate action,
+    // e.g., using GraphLense#mapDexEncodedMethod to look up the correct, up-to-date instance.
+    return obsolete;
+  }
+
+  private void setObsolete() {
+    // By assigning an Exception, you can see when/how this instance becomes obsolete v.s.
+    // when/how such obsolete instance is used.
+    obsolete = true;
+  }
+
   public DexEncodedMethod(
       DexMethod method,
       MethodAccessFlags accessFlags,
@@ -131,18 +153,22 @@
   }
 
   public boolean isProcessed() {
+    checkIfObsolete();
     return compilationState != CompilationState.NOT_PROCESSED;
   }
 
   public boolean isInstanceInitializer() {
+    checkIfObsolete();
     return accessFlags.isConstructor() && !accessFlags.isStatic();
   }
 
   public boolean isDefaultInitializer() {
+    checkIfObsolete();
     return isInstanceInitializer() && method.proto.parameters.isEmpty();
   }
 
   public boolean isClassInitializer() {
+    checkIfObsolete();
     return accessFlags.isConstructor() && accessFlags.isStatic();
   }
 
@@ -151,6 +177,7 @@
    * invoke-interface.
    */
   public boolean isVirtualMethod() {
+    checkIfObsolete();
     return !accessFlags.isStatic() && !accessFlags.isPrivate() && !accessFlags.isConstructor();
   }
 
@@ -159,18 +186,22 @@
    * and is non-abstract.
    */
   public boolean isNonAbstractVirtualMethod() {
+    checkIfObsolete();
     return isVirtualMethod() && !accessFlags.isAbstract();
   }
 
   public boolean isPublicized() {
+    checkIfObsolete();
     return optimizationInfo.isPublicized();
   }
 
   public boolean isPublicMethod() {
+    checkIfObsolete();
     return accessFlags.isPublic();
   }
 
   public boolean isPrivateMethod() {
+    checkIfObsolete();
     return accessFlags.isPrivate();
   }
 
@@ -178,6 +209,7 @@
    * Returns true if this method can be invoked via invoke-direct.
    */
   public boolean isDirectMethod() {
+    checkIfObsolete();
     return (accessFlags.isPrivate() || accessFlags.isConstructor()) && !accessFlags.isStatic();
   }
 
@@ -185,25 +217,27 @@
    * Returns true if this method can be invoked via invoke-static.
    */
   public boolean isStaticMethod() {
+    checkIfObsolete();
     return accessFlags.isStatic();
   }
 
-
   /**
    * Returns true if this method is synthetic.
    */
   public boolean isSyntheticMethod() {
+    checkIfObsolete();
     return accessFlags.isSynthetic();
   }
 
-
-  public boolean isInliningCandidate(DexEncodedMethod container, Reason inliningReason,
-      AppInfoWithSubtyping appInfo) {
+  public boolean isInliningCandidate(
+      DexEncodedMethod container, Reason inliningReason, AppInfoWithSubtyping appInfo) {
+    checkIfObsolete();
     return isInliningCandidate(container.method.getHolder(), inliningReason, appInfo);
   }
 
-  public boolean isInliningCandidate(DexType containerType, Reason inliningReason,
-      AppInfoWithSubtyping appInfo) {
+  public boolean isInliningCandidate(
+      DexType containerType, Reason inliningReason, AppInfoWithSubtyping appInfo) {
+    checkIfObsolete();
     if (isClassInitializer()) {
       // This will probably never happen but never inline a class initializer.
       return false;
@@ -229,6 +263,7 @@
   }
 
   public boolean markProcessed(ConstraintWithTarget state) {
+    checkIfObsolete();
     CompilationState prevCompilationState = compilationState;
     switch (state.constraint) {
       case ALWAYS:
@@ -251,16 +286,19 @@
   }
 
   public void markNotProcessed() {
+    checkIfObsolete();
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
   public IRCode buildIR(
       AppInfo appInfo, GraphLense graphLense, InternalOptions options, Origin origin) {
+    checkIfObsolete();
     return code == null ? null : code.buildIR(this, appInfo, graphLense, options, origin);
   }
 
   public IRCode buildInliningIRForTesting(
       InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
+    checkIfObsolete();
     return buildInliningIR(
         null, GraphLense.getIdentityLense(), options, valueNumberGenerator, null, Origin.unknown());
   }
@@ -272,32 +310,34 @@
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin) {
+    checkIfObsolete();
     return code.buildInliningIR(
         this, appInfo, graphLense, options, valueNumberGenerator, callerPosition, origin);
   }
 
   public void setCode(Code code) {
+    checkIfObsolete();
     voidCodeOwnership();
     this.code = code;
     setCodeOwnership();
   }
 
-  public void setCode(
-      IRCode ir,
-      RegisterAllocator registerAllocator,
-      InternalOptions options) {
+  public void setCode(IRCode ir, RegisterAllocator registerAllocator, InternalOptions options) {
+    checkIfObsolete();
     final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
     setCode(builder.build(method.getArity()));
   }
 
   @Override
   public String toString() {
+    checkIfObsolete();
     return "Encoded method " + method;
   }
 
   @Override
   public void collectIndexedItems(IndexedItemCollection indexedItems,
       DexMethod method, int instructionOffset) {
+    checkIfObsolete();
     this.method.collectIndexedItems(indexedItems);
     if (code != null) {
       code.collectIndexedItems(indexedItems, this.method);
@@ -324,10 +364,12 @@
   }
 
   public Code getCode() {
+    checkIfObsolete();
     return code;
   }
 
   public void removeCode() {
+    checkIfObsolete();
     voidCodeOwnership();
     code = null;
   }
@@ -345,34 +387,41 @@
   }
 
   public boolean hasDebugPositions() {
+    checkIfObsolete();
     assert code != null && code.isDexCode();
     return code.asDexCode().hasDebugPositions();
   }
 
   public int getClassFileVersion() {
+    checkIfObsolete();
     assert classFileVersion >= 0;
     return classFileVersion;
   }
 
   public boolean hasClassFileVersion() {
+    checkIfObsolete();
     return classFileVersion >= 0;
   }
 
   public void upgradeClassFileVersion(int version) {
+    checkIfObsolete();
     assert version >= 0;
     assert !hasClassFileVersion() || version >= getClassFileVersion();
     classFileVersion = version;
   }
 
   public String qualifiedName() {
+    checkIfObsolete();
     return method.qualifiedName();
   }
 
   public String descriptor() {
+    checkIfObsolete();
     return descriptor(NamingLens.getIdentityLens());
   }
 
   public String descriptor(NamingLens namingLens) {
+    checkIfObsolete();
     StringBuilder builder = new StringBuilder();
     builder.append("(");
     for (DexType type : method.proto.parameters.values) {
@@ -384,6 +433,7 @@
   }
 
   public String toSmaliString(ClassNameMapper naming) {
+    checkIfObsolete();
     StringBuilder builder = new StringBuilder();
     builder.append(".method ");
     builder.append(accessFlags.toSmaliString());
@@ -404,10 +454,12 @@
 
   @Override
   public String toSourceString() {
+    checkIfObsolete();
     return method.toSourceString();
   }
 
   public DexEncodedMethod toAbstractMethod() {
+    checkIfObsolete();
     // 'final' wants this to be *not* overridden, while 'abstract' wants this to be implemented in
     // a subtype, i.e., self contradict.
     assert !accessFlags.isFinal();
@@ -442,15 +494,22 @@
   }
 
   public DexEncodedMethod toEmptyThrowingMethodDex() {
+    checkIfObsolete();
     assert !shouldNotHaveCode();
     Builder builder = builder(this);
     Instruction insn[] = {new Const(0, 0), new Throw(0)};
     DexCode emptyThrowingCode = generateCodeFromTemplate(1, 0, insn);
     builder.setCode(emptyThrowingCode);
+    // Note that we are not marking this instance obsolete, since this util is only used by
+    // TreePruner while keeping non-live yet targeted, empty method. Such method can be retrieved
+    // again only during the 2nd round of tree sharking, and seeing an obsolete empty body v.s.
+    // seeing this empty throwing code do not matter.
+    // If things are changed, the cure point is obsolete instances inside RootSet.
     return builder.build();
   }
 
   public DexEncodedMethod toEmptyThrowingMethodCf() {
+    checkIfObsolete();
     assert !shouldNotHaveCode();
     Builder builder = builder(this);
     CfInstruction insn[] = {new CfConstNull(), new CfThrow()};
@@ -463,10 +522,13 @@
             Collections.emptyList(),
             Collections.emptyList());
     builder.setCode(emptyThrowingCode);
+    // Note that we are not marking this instance obsolete:
+    //   refer to Dex-backend version of this method above.
     return builder.build();
   }
 
   public DexEncodedMethod toMethodThatLogsError(DexItemFactory itemFactory) {
+    checkIfObsolete();
     Signature signature = MethodSignature.fromDexMethod(method);
     // TODO(herhut): Construct this out of parts to enable reuse, maybe even using descriptors.
     DexString message = itemFactory.createString(
@@ -508,29 +570,38 @@
     }
     Builder builder = builder(this);
     builder.setCode(code);
+    setObsolete();
     return builder.build();
   }
 
   public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) {
+    checkIfObsolete();
     if (this.method == method) {
       return this;
     }
     Builder builder = builder(this);
     builder.setMethod(method);
+    // TODO(b/112847660): Fix type fixers that use this method: ProguardMapApplier
+    // TODO(b/112847660): Fix type fixers that use this method: Staticizer
+    // TODO(b/112847660): Fix type fixers that use this method: VerticalClassMerger
+    // setObsolete();
     return builder.build();
   }
 
   public DexEncodedMethod toRenamedMethod(DexString name, DexItemFactory factory) {
+    checkIfObsolete();
     if (method.name == name) {
       return this;
     }
     DexMethod newMethod = factory.createMethod(method.holder, method.proto, name);
     Builder builder = builder(this);
     builder.setMethod(newMethod);
+    setObsolete();
     return builder.build();
   }
 
   public DexEncodedMethod toForwardingMethod(DexClass holder, DexItemFactory itemFactory) {
+    checkIfObsolete();
     assert accessFlags.isPublic();
     // Clear the final flag, as this method is now overwritten. Do this before creating the builder
     // for the forwarding method, as the forwarding method will copy the access flags from this,
@@ -566,14 +637,18 @@
               }));
     }
     builder.accessFlags.setSynthetic();
+    // Note that we are not marking this instance obsolete, since it is not: the newly synthesized
+    // forwarding method has a separate code that literally forwards to the current method.
     return builder.build();
   }
 
   public DexEncodedMethod toStaticMethodWithoutThis() {
+    checkIfObsolete();
     assert !accessFlags.isStatic();
     Builder builder = builder(this);
     builder.setStatic();
     builder.withoutThisParameter();
+    setObsolete();
     return builder.build();
   }
 
@@ -584,8 +659,9 @@
    * this will also update the highestSortingString to the index of the strings up to which the code
    * was rewritten to avoid rewriting again unless needed.
    */
-  public synchronized void rewriteCodeWithJumboStrings(ObjectToOffsetMapping mapping,
-      DexApplication application, boolean force) {
+  public synchronized void rewriteCodeWithJumboStrings(
+      ObjectToOffsetMapping mapping, DexApplication application, boolean force) {
+    checkIfObsolete();
     assert code == null || code.isDexCode();
     if (code == null) {
       return;
@@ -606,34 +682,42 @@
   }
 
   public String codeToString() {
+    checkIfObsolete();
     return code == null ? "<no code>" : code.toString(this, null);
   }
 
   @Override
   public DexMethod getKey() {
+    // Here, we can't check if the current instance of DexEncodedMethod is obsolete
+    // because itself can be used as a key while making mappings to avoid using obsolete instances.
     return method;
   }
 
   @Override
   public DexReference toReference() {
+    checkIfObsolete();
     return method;
   }
 
   @Override
   public boolean isDexEncodedMethod() {
+    checkIfObsolete();
     return true;
   }
 
   @Override
   public DexEncodedMethod asDexEncodedMethod() {
+    checkIfObsolete();
     return this;
   }
 
   public boolean hasAnnotation() {
+    checkIfObsolete();
     return !annotations.isEmpty() || !parameterAnnotationsList.isEmpty();
   }
 
   public void registerCodeReferences(UseRegistry registry) {
+    checkIfObsolete();
     if (code != null) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method);
@@ -957,10 +1041,12 @@
   }
 
   public OptimizationInfo getOptimizationInfo() {
+    checkIfObsolete();
     return optimizationInfo;
   }
 
   public void copyMetadataFromInlinee(DexEncodedMethod inlinee) {
+    checkIfObsolete();
     // Record that the current method uses identifier name string if the inlinee did so.
     if (inlinee.getOptimizationInfo().useIdentifierNameString()) {
       markUseIdentifierNameString();
@@ -1034,26 +1120,31 @@
 
   @Override
   public DexEncodedMethod asResultOfResolve() {
+    checkIfObsolete();
     return this;
   }
 
   @Override
   public DexEncodedMethod asSingleTarget() {
+    checkIfObsolete();
     return this;
   }
 
   @Override
   public boolean hasSingleTarget() {
+    checkIfObsolete();
     return true;
   }
 
   @Override
   public List<DexEncodedMethod> asListOfTargets() {
+    checkIfObsolete();
     return Collections.singletonList(this);
   }
 
   @Override
   public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
+    checkIfObsolete();
     consumer.accept(this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 16e1b47..45de8f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -165,9 +165,15 @@
           .sorted(DexEncodedMethod::slowCompare)
           .collect(Collectors.toList());
       for (DexEncodedMethod method : methods) {
-        converter.processMethod(method, feedback, x -> false, CallSiteInformation.empty(),
+        DexEncodedMethod mappedMethod =
+            converter.getGraphLense().mapDexEncodedMethod(appInfo, method);
+        converter.processMethod(
+            mappedMethod,
+            feedback,
+            x -> false,
+            CallSiteInformation.empty(),
             Outliner::noProcessing);
-        assert method.isProcessed();
+        assert mappedMethod.isProcessed();
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
index 4f6c33c..9bdd55e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -13,10 +14,17 @@
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableMap;
+import java.util.Map;
 
 class ClassStaticizerGraphLense extends NestedGraphLense {
-  ClassStaticizerGraphLense(GraphLense previous, DexItemFactory factory,
-      BiMap<DexField, DexField> fieldMapping, BiMap<DexMethod, DexMethod> methodMapping) {
+  private final Map<DexEncodedMethod, DexEncodedMethod> staticizedMethods;
+
+  ClassStaticizerGraphLense(
+      GraphLense previous,
+      DexItemFactory factory,
+      BiMap<DexField, DexField> fieldMapping,
+      BiMap<DexMethod, DexMethod> methodMapping,
+      Map<DexEncodedMethod, DexEncodedMethod> encodedMethodMapping) {
     super(ImmutableMap.of(),
         methodMapping,
         fieldMapping,
@@ -24,6 +32,7 @@
         methodMapping.inverse(),
         previous,
         factory);
+    staticizedMethods = encodedMethodMapping;
   }
 
   @Override
@@ -36,4 +45,9 @@
     }
     return super.mapInvocationType(newMethod, originalMethod, context, type);
   }
+
+  @Override
+  public DexEncodedMethod mapDexEncodedMethod(AppInfo appInfo, DexEncodedMethod original) {
+    return super.mapDexEncodedMethod(appInfo, staticizedMethods.getOrDefault(original, original));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index bab3ec4..5b930fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -33,6 +33,7 @@
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -323,6 +324,7 @@
 
   private Set<DexEncodedMethod> staticizeMethodSymbols() {
     BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+    Map<DexEncodedMethod, DexEncodedMethod> encodedMethodMapping = new HashMap<>();
     BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
 
     Set<DexEncodedMethod> staticizedMethods = Sets.newIdentityHashSet();
@@ -339,6 +341,7 @@
           newDirectMethods.add(staticizedMethod);
           staticizedMethods.add(staticizedMethod);
           methodMapping.put(method.method, staticizedMethod.method);
+          encodedMethodMapping.put(method, staticizedMethod);
         }
       }
       candidateClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
@@ -364,7 +367,8 @@
               classStaticizer.converter.getGraphLense(),
               classStaticizer.factory,
               fieldMapping,
-              methodMapping));
+              methodMapping,
+              encodedMethodMapping));
     }
     return staticizedMethods;
   }