Merge commit 'ff0e81d500a7f7cf700b77905f42d8d67e545b88' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 15af102..39ed0b5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
@@ -99,7 +98,6 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.io.ByteStreams;
 import java.io.ByteArrayOutputStream;
@@ -791,13 +789,7 @@
       // Remove unneeded visibility bridges that have been inserted for member rebinding.
       // This can only be done if we have AppInfoWithLiveness.
       if (appView.appInfo().hasLiveness()) {
-        ImmutableSet.Builder<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.builder();
-        new VisibilityBridgeRemover(
-                appView.withLiveness(),
-                unneededVisibilityBridgeMethod ->
-                    unneededVisibilityBridgeMethods.add(unneededVisibilityBridgeMethod.method))
-            .run();
-        appView.setUnneededVisibilityBridgeMethods(unneededVisibilityBridgeMethods.build());
+        new VisibilityBridgeRemover(appView.withLiveness()).run();
       } else {
         // If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When
         // we are not shrinking, we can't move visibility bridges. In principle, though, it would be
diff --git a/src/main/java/com/android/tools/r8/androidapi/AvailableApiExceptions.java b/src/main/java/com/android/tools/r8/androidapi/AvailableApiExceptions.java
new file mode 100644
index 0000000..c44f1f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AvailableApiExceptions.java
@@ -0,0 +1,405 @@
+// 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.androidapi;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.Set;
+
+/**
+ * Provides a mapping of API levels to exceptions introduced at that API level.
+ *
+ * <p>The mapping is only provided up to excluding the API level 21 (Android L) at which point the
+ * verification issue for unresolved exception types was fixed.
+ *
+ * <p>See b/153514654 and b/131349148.
+ *
+ * <p>See GenerateAvailableApiExceptions.main in tests module for generating the buildMap function.
+ */
+public class AvailableApiExceptions {
+
+  private final Set<DexType> exceptions;
+
+  public AvailableApiExceptions(InternalOptions options) {
+    assert options.minApiLevel < AndroidApiLevel.L.getLevel();
+    exceptions = build(options.itemFactory, options.minApiLevel);
+  }
+
+  public boolean canCauseVerificationError(DexType type) {
+    return !exceptions.contains(type);
+  }
+
+  /** The content of this method can be regenerated with GenerateAvailableExceptions.main. */
+  public static Set<DexType> build(DexItemFactory factory, int minApiLevel) {
+    Set<DexType> types = SetUtils.newIdentityHashSet(333);
+    if (minApiLevel >= 1) {
+      types.add(factory.createType("Landroid/app/PendingIntent$CanceledException;"));
+      types.add(factory.createType("Landroid/content/ActivityNotFoundException;"));
+      types.add(factory.createType("Landroid/content/IntentFilter$MalformedMimeTypeException;"));
+      types.add(factory.createType("Landroid/content/ReceiverCallNotAllowedException;"));
+      types.add(factory.createType("Landroid/content/pm/PackageManager$NameNotFoundException;"));
+      types.add(factory.createType("Landroid/content/res/Resources$NotFoundException;"));
+      types.add(factory.createType("Landroid/database/CursorIndexOutOfBoundsException;"));
+      types.add(factory.createType("Landroid/database/SQLException;"));
+      types.add(factory.createType("Landroid/database/StaleDataException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteAbortException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteConstraintException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteDatabaseCorruptException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteDiskIOException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteDoneException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteFullException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteMisuseException;"));
+      types.add(factory.createType("Landroid/net/ParseException;"));
+      types.add(factory.createType("Landroid/opengl/GLException;"));
+      types.add(factory.createType("Landroid/os/BadParcelableException;"));
+      types.add(factory.createType("Landroid/os/DeadObjectException;"));
+      types.add(factory.createType("Landroid/os/ParcelFormatException;"));
+      types.add(factory.createType("Landroid/os/RemoteException;"));
+      types.add(factory.createType("Landroid/provider/Settings$SettingNotFoundException;"));
+      types.add(factory.createType("Landroid/test/AssertionFailedError;"));
+      types.add(factory.createType("Landroid/test/ComparisonFailure;"));
+      types.add(factory.createType("Landroid/util/AndroidException;"));
+      types.add(factory.createType("Landroid/util/AndroidRuntimeException;"));
+      types.add(factory.createType("Landroid/util/TimeFormatException;"));
+      types.add(factory.createType("Landroid/view/InflateException;"));
+      types.add(factory.createType("Landroid/view/Surface$OutOfResourcesException;"));
+      types.add(factory.createType("Landroid/view/SurfaceHolder$BadSurfaceTypeException;"));
+      types.add(factory.createType("Landroid/view/WindowManager$BadTokenException;"));
+      types.add(factory.createType("Landroid/widget/RemoteViews$ActionException;"));
+      types.add(factory.createType("Ljava/io/CharConversionException;"));
+      types.add(factory.createType("Ljava/io/EOFException;"));
+      types.add(factory.createType("Ljava/io/FileNotFoundException;"));
+      types.add(factory.createType("Ljava/io/IOException;"));
+      types.add(factory.createType("Ljava/io/InterruptedIOException;"));
+      types.add(factory.createType("Ljava/io/InvalidClassException;"));
+      types.add(factory.createType("Ljava/io/InvalidObjectException;"));
+      types.add(factory.createType("Ljava/io/NotActiveException;"));
+      types.add(factory.createType("Ljava/io/NotSerializableException;"));
+      types.add(factory.createType("Ljava/io/ObjectStreamException;"));
+      types.add(factory.createType("Ljava/io/OptionalDataException;"));
+      types.add(factory.createType("Ljava/io/StreamCorruptedException;"));
+      types.add(factory.createType("Ljava/io/SyncFailedException;"));
+      types.add(factory.createType("Ljava/io/UTFDataFormatException;"));
+      types.add(factory.createType("Ljava/io/UnsupportedEncodingException;"));
+      types.add(factory.createType("Ljava/io/WriteAbortedException;"));
+      types.add(factory.createType("Ljava/lang/AbstractMethodError;"));
+      types.add(factory.createType("Ljava/lang/ArithmeticException;"));
+      types.add(factory.createType("Ljava/lang/ArrayIndexOutOfBoundsException;"));
+      types.add(factory.createType("Ljava/lang/ArrayStoreException;"));
+      types.add(factory.createType("Ljava/lang/AssertionError;"));
+      types.add(factory.createType("Ljava/lang/ClassCastException;"));
+      types.add(factory.createType("Ljava/lang/ClassCircularityError;"));
+      types.add(factory.createType("Ljava/lang/ClassFormatError;"));
+      types.add(factory.createType("Ljava/lang/ClassNotFoundException;"));
+      types.add(factory.createType("Ljava/lang/CloneNotSupportedException;"));
+      types.add(factory.createType("Ljava/lang/EnumConstantNotPresentException;"));
+      types.add(factory.createType("Ljava/lang/Error;"));
+      types.add(factory.createType("Ljava/lang/Exception;"));
+      types.add(factory.createType("Ljava/lang/ExceptionInInitializerError;"));
+      types.add(factory.createType("Ljava/lang/IllegalAccessError;"));
+      types.add(factory.createType("Ljava/lang/IllegalAccessException;"));
+      types.add(factory.createType("Ljava/lang/IllegalArgumentException;"));
+      types.add(factory.createType("Ljava/lang/IllegalMonitorStateException;"));
+      types.add(factory.createType("Ljava/lang/IllegalStateException;"));
+      types.add(factory.createType("Ljava/lang/IllegalThreadStateException;"));
+      types.add(factory.createType("Ljava/lang/IncompatibleClassChangeError;"));
+      types.add(factory.createType("Ljava/lang/IndexOutOfBoundsException;"));
+      types.add(factory.createType("Ljava/lang/InstantiationError;"));
+      types.add(factory.createType("Ljava/lang/InstantiationException;"));
+      types.add(factory.createType("Ljava/lang/InternalError;"));
+      types.add(factory.createType("Ljava/lang/InterruptedException;"));
+      types.add(factory.createType("Ljava/lang/LinkageError;"));
+      types.add(factory.createType("Ljava/lang/NegativeArraySizeException;"));
+      types.add(factory.createType("Ljava/lang/NoClassDefFoundError;"));
+      types.add(factory.createType("Ljava/lang/NoSuchFieldError;"));
+      types.add(factory.createType("Ljava/lang/NoSuchFieldException;"));
+      types.add(factory.createType("Ljava/lang/NoSuchMethodError;"));
+      types.add(factory.createType("Ljava/lang/NoSuchMethodException;"));
+      types.add(factory.createType("Ljava/lang/NullPointerException;"));
+      types.add(factory.createType("Ljava/lang/NumberFormatException;"));
+      types.add(factory.createType("Ljava/lang/OutOfMemoryError;"));
+      types.add(factory.createType("Ljava/lang/RuntimeException;"));
+      types.add(factory.createType("Ljava/lang/SecurityException;"));
+      types.add(factory.createType("Ljava/lang/StackOverflowError;"));
+      types.add(factory.createType("Ljava/lang/StringIndexOutOfBoundsException;"));
+      types.add(factory.createType("Ljava/lang/ThreadDeath;"));
+      types.add(factory.createType("Ljava/lang/Throwable;"));
+      types.add(factory.createType("Ljava/lang/TypeNotPresentException;"));
+      types.add(factory.createType("Ljava/lang/UnknownError;"));
+      types.add(factory.createType("Ljava/lang/UnsatisfiedLinkError;"));
+      types.add(factory.createType("Ljava/lang/UnsupportedClassVersionError;"));
+      types.add(factory.createType("Ljava/lang/UnsupportedOperationException;"));
+      types.add(factory.createType("Ljava/lang/VerifyError;"));
+      types.add(factory.createType("Ljava/lang/VirtualMachineError;"));
+      types.add(factory.createType("Ljava/lang/annotation/AnnotationFormatError;"));
+      types.add(factory.createType("Ljava/lang/annotation/AnnotationTypeMismatchException;"));
+      types.add(factory.createType("Ljava/lang/annotation/IncompleteAnnotationException;"));
+      types.add(factory.createType("Ljava/lang/reflect/GenericSignatureFormatError;"));
+      types.add(factory.createType("Ljava/lang/reflect/InvocationTargetException;"));
+      types.add(factory.createType("Ljava/lang/reflect/MalformedParameterizedTypeException;"));
+      types.add(factory.createType("Ljava/lang/reflect/UndeclaredThrowableException;"));
+      types.add(factory.createType("Ljava/net/BindException;"));
+      types.add(factory.createType("Ljava/net/ConnectException;"));
+      types.add(factory.createType("Ljava/net/HttpRetryException;"));
+      types.add(factory.createType("Ljava/net/MalformedURLException;"));
+      types.add(factory.createType("Ljava/net/NoRouteToHostException;"));
+      types.add(factory.createType("Ljava/net/PortUnreachableException;"));
+      types.add(factory.createType("Ljava/net/ProtocolException;"));
+      types.add(factory.createType("Ljava/net/SocketException;"));
+      types.add(factory.createType("Ljava/net/SocketTimeoutException;"));
+      types.add(factory.createType("Ljava/net/URISyntaxException;"));
+      types.add(factory.createType("Ljava/net/UnknownHostException;"));
+      types.add(factory.createType("Ljava/net/UnknownServiceException;"));
+      types.add(factory.createType("Ljava/nio/BufferOverflowException;"));
+      types.add(factory.createType("Ljava/nio/BufferUnderflowException;"));
+      types.add(factory.createType("Ljava/nio/InvalidMarkException;"));
+      types.add(factory.createType("Ljava/nio/ReadOnlyBufferException;"));
+      types.add(factory.createType("Ljava/nio/channels/AlreadyConnectedException;"));
+      types.add(factory.createType("Ljava/nio/channels/AsynchronousCloseException;"));
+      types.add(factory.createType("Ljava/nio/channels/CancelledKeyException;"));
+      types.add(factory.createType("Ljava/nio/channels/ClosedByInterruptException;"));
+      types.add(factory.createType("Ljava/nio/channels/ClosedChannelException;"));
+      types.add(factory.createType("Ljava/nio/channels/ClosedSelectorException;"));
+      types.add(factory.createType("Ljava/nio/channels/ConnectionPendingException;"));
+      types.add(factory.createType("Ljava/nio/channels/FileLockInterruptionException;"));
+      types.add(factory.createType("Ljava/nio/channels/IllegalBlockingModeException;"));
+      types.add(factory.createType("Ljava/nio/channels/IllegalSelectorException;"));
+      types.add(factory.createType("Ljava/nio/channels/NoConnectionPendingException;"));
+      types.add(factory.createType("Ljava/nio/channels/NonReadableChannelException;"));
+      types.add(factory.createType("Ljava/nio/channels/NonWritableChannelException;"));
+      types.add(factory.createType("Ljava/nio/channels/NotYetBoundException;"));
+      types.add(factory.createType("Ljava/nio/channels/NotYetConnectedException;"));
+      types.add(factory.createType("Ljava/nio/channels/OverlappingFileLockException;"));
+      types.add(factory.createType("Ljava/nio/channels/UnresolvedAddressException;"));
+      types.add(factory.createType("Ljava/nio/channels/UnsupportedAddressTypeException;"));
+      types.add(factory.createType("Ljava/nio/charset/CharacterCodingException;"));
+      types.add(factory.createType("Ljava/nio/charset/CoderMalfunctionError;"));
+      types.add(factory.createType("Ljava/nio/charset/IllegalCharsetNameException;"));
+      types.add(factory.createType("Ljava/nio/charset/MalformedInputException;"));
+      types.add(factory.createType("Ljava/nio/charset/UnmappableCharacterException;"));
+      types.add(factory.createType("Ljava/nio/charset/UnsupportedCharsetException;"));
+      types.add(factory.createType("Ljava/security/AccessControlException;"));
+      types.add(factory.createType("Ljava/security/DigestException;"));
+      types.add(factory.createType("Ljava/security/GeneralSecurityException;"));
+      types.add(factory.createType("Ljava/security/InvalidAlgorithmParameterException;"));
+      types.add(factory.createType("Ljava/security/InvalidKeyException;"));
+      types.add(factory.createType("Ljava/security/InvalidParameterException;"));
+      types.add(factory.createType("Ljava/security/KeyException;"));
+      types.add(factory.createType("Ljava/security/KeyManagementException;"));
+      types.add(factory.createType("Ljava/security/KeyStoreException;"));
+      types.add(factory.createType("Ljava/security/NoSuchAlgorithmException;"));
+      types.add(factory.createType("Ljava/security/NoSuchProviderException;"));
+      types.add(factory.createType("Ljava/security/PrivilegedActionException;"));
+      types.add(factory.createType("Ljava/security/ProviderException;"));
+      types.add(factory.createType("Ljava/security/SignatureException;"));
+      types.add(factory.createType("Ljava/security/UnrecoverableEntryException;"));
+      types.add(factory.createType("Ljava/security/UnrecoverableKeyException;"));
+      types.add(factory.createType("Ljava/security/acl/AclNotFoundException;"));
+      types.add(factory.createType("Ljava/security/acl/LastOwnerException;"));
+      types.add(factory.createType("Ljava/security/acl/NotOwnerException;"));
+      types.add(factory.createType("Ljava/security/cert/CRLException;"));
+      types.add(factory.createType("Ljava/security/cert/CertPathBuilderException;"));
+      types.add(factory.createType("Ljava/security/cert/CertPathValidatorException;"));
+      types.add(factory.createType("Ljava/security/cert/CertStoreException;"));
+      types.add(factory.createType("Ljava/security/cert/CertificateEncodingException;"));
+      types.add(factory.createType("Ljava/security/cert/CertificateException;"));
+      types.add(factory.createType("Ljava/security/cert/CertificateExpiredException;"));
+      types.add(factory.createType("Ljava/security/cert/CertificateNotYetValidException;"));
+      types.add(factory.createType("Ljava/security/cert/CertificateParsingException;"));
+      types.add(factory.createType("Ljava/security/spec/InvalidKeySpecException;"));
+      types.add(factory.createType("Ljava/security/spec/InvalidParameterSpecException;"));
+      types.add(factory.createType("Ljava/sql/BatchUpdateException;"));
+      types.add(factory.createType("Ljava/sql/DataTruncation;"));
+      types.add(factory.createType("Ljava/sql/SQLException;"));
+      types.add(factory.createType("Ljava/sql/SQLWarning;"));
+      types.add(factory.createType("Ljava/text/ParseException;"));
+      types.add(factory.createType("Ljava/util/ConcurrentModificationException;"));
+      types.add(factory.createType("Ljava/util/DuplicateFormatFlagsException;"));
+      types.add(factory.createType("Ljava/util/EmptyStackException;"));
+      types.add(factory.createType("Ljava/util/FormatFlagsConversionMismatchException;"));
+      types.add(factory.createType("Ljava/util/FormatterClosedException;"));
+      types.add(factory.createType("Ljava/util/IllegalFormatCodePointException;"));
+      types.add(factory.createType("Ljava/util/IllegalFormatConversionException;"));
+      types.add(factory.createType("Ljava/util/IllegalFormatException;"));
+      types.add(factory.createType("Ljava/util/IllegalFormatFlagsException;"));
+      types.add(factory.createType("Ljava/util/IllegalFormatPrecisionException;"));
+      types.add(factory.createType("Ljava/util/IllegalFormatWidthException;"));
+      types.add(factory.createType("Ljava/util/InputMismatchException;"));
+      types.add(factory.createType("Ljava/util/InvalidPropertiesFormatException;"));
+      types.add(factory.createType("Ljava/util/MissingFormatArgumentException;"));
+      types.add(factory.createType("Ljava/util/MissingFormatWidthException;"));
+      types.add(factory.createType("Ljava/util/MissingResourceException;"));
+      types.add(factory.createType("Ljava/util/NoSuchElementException;"));
+      types.add(factory.createType("Ljava/util/TooManyListenersException;"));
+      types.add(factory.createType("Ljava/util/UnknownFormatConversionException;"));
+      types.add(factory.createType("Ljava/util/UnknownFormatFlagsException;"));
+      types.add(factory.createType("Ljava/util/concurrent/BrokenBarrierException;"));
+      types.add(factory.createType("Ljava/util/concurrent/CancellationException;"));
+      types.add(factory.createType("Ljava/util/concurrent/ExecutionException;"));
+      types.add(factory.createType("Ljava/util/concurrent/RejectedExecutionException;"));
+      types.add(factory.createType("Ljava/util/concurrent/TimeoutException;"));
+      types.add(factory.createType("Ljava/util/jar/JarException;"));
+      types.add(factory.createType("Ljava/util/prefs/BackingStoreException;"));
+      types.add(factory.createType("Ljava/util/prefs/InvalidPreferencesFormatException;"));
+      types.add(factory.createType("Ljava/util/regex/PatternSyntaxException;"));
+      types.add(factory.createType("Ljava/util/zip/DataFormatException;"));
+      types.add(factory.createType("Ljava/util/zip/ZipException;"));
+      types.add(factory.createType("Ljavax/crypto/BadPaddingException;"));
+      types.add(factory.createType("Ljavax/crypto/ExemptionMechanismException;"));
+      types.add(factory.createType("Ljavax/crypto/IllegalBlockSizeException;"));
+      types.add(factory.createType("Ljavax/crypto/NoSuchPaddingException;"));
+      types.add(factory.createType("Ljavax/crypto/ShortBufferException;"));
+      types.add(factory.createType("Ljavax/net/ssl/SSLException;"));
+      types.add(factory.createType("Ljavax/net/ssl/SSLHandshakeException;"));
+      types.add(factory.createType("Ljavax/net/ssl/SSLKeyException;"));
+      types.add(factory.createType("Ljavax/net/ssl/SSLPeerUnverifiedException;"));
+      types.add(factory.createType("Ljavax/net/ssl/SSLProtocolException;"));
+      types.add(factory.createType("Ljavax/security/auth/DestroyFailedException;"));
+      types.add(factory.createType("Ljavax/security/auth/callback/UnsupportedCallbackException;"));
+      types.add(factory.createType("Ljavax/security/auth/login/LoginException;"));
+      types.add(factory.createType("Ljavax/security/cert/CertificateEncodingException;"));
+      types.add(factory.createType("Ljavax/security/cert/CertificateException;"));
+      types.add(factory.createType("Ljavax/security/cert/CertificateExpiredException;"));
+      types.add(factory.createType("Ljavax/security/cert/CertificateNotYetValidException;"));
+      types.add(factory.createType("Ljavax/security/cert/CertificateParsingException;"));
+      types.add(factory.createType("Ljavax/xml/parsers/FactoryConfigurationError;"));
+      types.add(factory.createType("Ljavax/xml/parsers/ParserConfigurationException;"));
+      types.add(factory.createType("Ljunit/framework/AssertionFailedError;"));
+      types.add(factory.createType("Ljunit/framework/ComparisonFailure;"));
+      types.add(factory.createType("Lorg/apache/http/ConnectionClosedException;"));
+      types.add(factory.createType("Lorg/apache/http/HttpException;"));
+      types.add(factory.createType("Lorg/apache/http/MalformedChunkCodingException;"));
+      types.add(factory.createType("Lorg/apache/http/MethodNotSupportedException;"));
+      types.add(factory.createType("Lorg/apache/http/NoHttpResponseException;"));
+      types.add(factory.createType("Lorg/apache/http/ParseException;"));
+      types.add(factory.createType("Lorg/apache/http/ProtocolException;"));
+      types.add(factory.createType("Lorg/apache/http/UnsupportedHttpVersionException;"));
+      types.add(factory.createType("Lorg/apache/http/auth/AuthenticationException;"));
+      types.add(factory.createType("Lorg/apache/http/auth/InvalidCredentialsException;"));
+      types.add(factory.createType("Lorg/apache/http/auth/MalformedChallengeException;"));
+      types.add(factory.createType("Lorg/apache/http/client/CircularRedirectException;"));
+      types.add(factory.createType("Lorg/apache/http/client/ClientProtocolException;"));
+      types.add(factory.createType("Lorg/apache/http/client/HttpResponseException;"));
+      types.add(factory.createType("Lorg/apache/http/client/NonRepeatableRequestException;"));
+      types.add(factory.createType("Lorg/apache/http/client/RedirectException;"));
+      types.add(factory.createType("Lorg/apache/http/conn/ConnectTimeoutException;"));
+      types.add(factory.createType("Lorg/apache/http/conn/ConnectionPoolTimeoutException;"));
+      types.add(factory.createType("Lorg/apache/http/conn/HttpHostConnectException;"));
+      types.add(factory.createType("Lorg/apache/http/cookie/MalformedCookieException;"));
+      types.add(factory.createType("Lorg/apache/http/impl/auth/NTLMEngineException;"));
+      types.add(
+          factory.createType("Lorg/apache/http/impl/auth/UnsupportedDigestAlgorithmException;"));
+      types.add(factory.createType("Lorg/apache/http/impl/client/TunnelRefusedException;"));
+      types.add(factory.createType("Lorg/apache/http/impl/cookie/DateParseException;"));
+      types.add(factory.createType("Lorg/json/JSONException;"));
+      types.add(factory.createType("Lorg/w3c/dom/DOMException;"));
+      types.add(factory.createType("Lorg/xml/sax/SAXException;"));
+      types.add(factory.createType("Lorg/xml/sax/SAXNotRecognizedException;"));
+      types.add(factory.createType("Lorg/xml/sax/SAXNotSupportedException;"));
+      types.add(factory.createType("Lorg/xml/sax/SAXParseException;"));
+      types.add(factory.createType("Lorg/xmlpull/v1/XmlPullParserException;"));
+    }
+    if (minApiLevel >= 4) {
+      types.add(factory.createType("Landroid/content/IntentSender$SendIntentException;"));
+    }
+    if (minApiLevel >= 5) {
+      types.add(factory.createType("Landroid/accounts/AccountsException;"));
+      types.add(factory.createType("Landroid/accounts/AuthenticatorException;"));
+      types.add(factory.createType("Landroid/accounts/NetworkErrorException;"));
+      types.add(factory.createType("Landroid/accounts/OperationCanceledException;"));
+      types.add(factory.createType("Landroid/content/OperationApplicationException;"));
+    }
+    if (minApiLevel >= 8) {
+      types.add(factory.createType("Ljavax/xml/datatype/DatatypeConfigurationException;"));
+      types.add(factory.createType("Ljavax/xml/transform/TransformerConfigurationException;"));
+      types.add(factory.createType("Ljavax/xml/transform/TransformerException;"));
+      types.add(factory.createType("Ljavax/xml/transform/TransformerFactoryConfigurationError;"));
+      types.add(factory.createType("Ljavax/xml/xpath/XPathException;"));
+      types.add(factory.createType("Ljavax/xml/xpath/XPathExpressionException;"));
+      types.add(factory.createType("Ljavax/xml/xpath/XPathFactoryConfigurationException;"));
+      types.add(factory.createType("Ljavax/xml/xpath/XPathFunctionException;"));
+      types.add(factory.createType("Lorg/w3c/dom/ls/LSException;"));
+    }
+    if (minApiLevel >= 9) {
+      types.add(factory.createType("Landroid/net/sip/SipException;"));
+      types.add(factory.createType("Landroid/nfc/FormatException;"));
+      types.add(factory.createType("Ljava/io/IOError;"));
+      types.add(factory.createType("Ljava/sql/SQLClientInfoException;"));
+      types.add(factory.createType("Ljava/sql/SQLDataException;"));
+      types.add(factory.createType("Ljava/sql/SQLFeatureNotSupportedException;"));
+      types.add(factory.createType("Ljava/sql/SQLIntegrityConstraintViolationException;"));
+      types.add(factory.createType("Ljava/sql/SQLInvalidAuthorizationSpecException;"));
+      types.add(factory.createType("Ljava/sql/SQLNonTransientConnectionException;"));
+      types.add(factory.createType("Ljava/sql/SQLNonTransientException;"));
+      types.add(factory.createType("Ljava/sql/SQLRecoverableException;"));
+      types.add(factory.createType("Ljava/sql/SQLSyntaxErrorException;"));
+      types.add(factory.createType("Ljava/sql/SQLTimeoutException;"));
+      types.add(factory.createType("Ljava/sql/SQLTransactionRollbackException;"));
+      types.add(factory.createType("Ljava/sql/SQLTransientConnectionException;"));
+      types.add(factory.createType("Ljava/sql/SQLTransientException;"));
+      types.add(factory.createType("Ljava/util/ServiceConfigurationError;"));
+      types.add(factory.createType("Ljava/util/zip/ZipError;"));
+    }
+    if (minApiLevel >= 10) {
+      types.add(factory.createType("Landroid/nfc/TagLostException;"));
+    }
+    if (minApiLevel >= 11) {
+      types.add(factory.createType("Landroid/app/Fragment$InstantiationException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteAccessPermException;"));
+      types.add(
+          factory.createType(
+              "Landroid/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteBlobTooBigException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteCantOpenDatabaseException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteDatabaseLockedException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteDatatypeMismatchException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteOutOfMemoryException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteReadOnlyDatabaseException;"));
+      types.add(factory.createType("Landroid/database/sqlite/SQLiteTableLockedException;"));
+      types.add(factory.createType("Landroid/graphics/SurfaceTexture$OutOfResourcesException;"));
+      types.add(factory.createType("Landroid/os/NetworkOnMainThreadException;"));
+      types.add(factory.createType("Landroid/renderscript/RSDriverException;"));
+      types.add(factory.createType("Landroid/renderscript/RSIllegalArgumentException;"));
+      types.add(factory.createType("Landroid/renderscript/RSInvalidStateException;"));
+      types.add(factory.createType("Landroid/renderscript/RSRuntimeException;"));
+      types.add(factory.createType("Landroid/util/Base64DataException;"));
+      types.add(factory.createType("Landroid/util/MalformedJsonException;"));
+      types.add(factory.createType("Landroid/view/KeyCharacterMap$UnavailableException;"));
+    }
+    if (minApiLevel >= 14) {
+      types.add(factory.createType("Landroid/security/KeyChainException;"));
+      types.add(factory.createType("Landroid/util/NoSuchPropertyException;"));
+    }
+    if (minApiLevel >= 15) {
+      types.add(factory.createType("Landroid/os/TransactionTooLargeException;"));
+    }
+    if (minApiLevel >= 16) {
+      types.add(factory.createType("Landroid/media/MediaCodec$CryptoException;"));
+      types.add(factory.createType("Landroid/media/MediaCryptoException;"));
+      types.add(factory.createType("Landroid/os/OperationCanceledException;"));
+    }
+    if (minApiLevel >= 17) {
+      types.add(factory.createType("Landroid/view/WindowManager$InvalidDisplayException;"));
+    }
+    if (minApiLevel >= 18) {
+      types.add(factory.createType("Landroid/media/DeniedByServerException;"));
+      types.add(factory.createType("Landroid/media/MediaDrmException;"));
+      types.add(factory.createType("Landroid/media/NotProvisionedException;"));
+      types.add(factory.createType("Landroid/media/UnsupportedSchemeException;"));
+    }
+    if (minApiLevel >= 19) {
+      types.add(factory.createType("Landroid/media/ResourceBusyException;"));
+      types.add(
+          factory.createType("Landroid/os/ParcelFileDescriptor$FileDescriptorDetachedException;"));
+      types.add(factory.createType("Ljava/lang/ReflectiveOperationException;"));
+      types.add(factory.createType("Ljavax/crypto/AEADBadTagException;"));
+    }
+    return types;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index a9ff477..4eee167 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -633,7 +633,7 @@
 
     @Override
     public DexString getRenamedName(DexMethod method) {
-      assert namingLens.checkTargetCanBeTranslated(method);
+      assert namingLens.verifyRenamingConsistentWithResolution(method);
       return namingLens.lookupName(method);
     }
 
@@ -729,7 +729,7 @@
 
     @Override
     public DexString getRenamedName(DexMethod method) {
-      assert namingLens.checkTargetCanBeTranslated(method);
+      assert namingLens.verifyRenamingConsistentWithResolution(method);
       return namingLens.lookupName(method);
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 20216c4..0e4b9cf 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -25,10 +25,8 @@
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -64,7 +62,6 @@
   private boolean allCodeProcessed = false;
   private Predicate<DexType> classesEscapingIntoLibrary = Predicates.alwaysTrue();
   private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
-  private Set<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.of();
   private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
   private EnumValueInfoMapCollection unboxedEnums = EnumValueInfoMapCollection.empty();
@@ -350,14 +347,6 @@
     this.rootSet = rootSet;
   }
 
-  public Set<DexMethod> unneededVisibilityBridgeMethods() {
-    return unneededVisibilityBridgeMethods;
-  }
-
-  public void setUnneededVisibilityBridgeMethods(Set<DexMethod> unneededVisibilityBridgeMethods) {
-    this.unneededVisibilityBridgeMethods = unneededVisibilityBridgeMethods;
-  }
-
   public MergedClassesCollection allMergedClasses() {
     MergedClassesCollection collection = new MergedClassesCollection();
     if (horizontallyMergedLambdaClasses != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 9dd0398..f8043c3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -150,8 +150,8 @@
     methodCollection.addDirectMethods(methods);
   }
 
-  public void removeDirectMethod(DexMethod method) {
-    methodCollection.removeDirectMethod(method);
+  public void removeMethod(DexMethod method) {
+    methodCollection.removeMethod(method);
   }
 
   public void setDirectMethods(DexEncodedMethod[] methods) {
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 20337fd..03f1415 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -257,6 +257,10 @@
     assert parameterAnnotationsList != null;
   }
 
+  public DexType returnType() {
+    return method.proto.returnType;
+  }
+
   public ParameterAnnotationsList liveParameterAnnotations(AppView<AppInfoWithLiveness> appView) {
     return parameterAnnotationsList.keepIf(
         annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
@@ -299,6 +303,10 @@
     return accessFlags.isAbstract();
   }
 
+  public boolean isBridge() {
+    return accessFlags.isBridge();
+  }
+
   public boolean isFinal() {
     return accessFlags.isFinal();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 4eda679..5b05c3f 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -12,6 +12,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -82,25 +83,42 @@
     assert verifyNoDuplicateMethods();
   }
 
-  private void removeDirectMethod(int index) {
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length - 1];
-    System.arraycopy(directMethods, 0, newMethods, 0, index);
-    System.arraycopy(directMethods, index + 1, newMethods, index, directMethods.length - index - 1);
-    directMethods = newMethods;
+  @Override
+  DexEncodedMethod removeMethod(DexMethod method) {
+    DexEncodedMethod removedDirectMethod =
+        removeMethodHelper(
+            method, directMethods, newDirectMethods -> directMethods = newDirectMethods);
+    if (removedDirectMethod != null) {
+      assert belongsToDirectPool(removedDirectMethod);
+      return removedDirectMethod;
+    }
+    DexEncodedMethod removedVirtualMethod =
+        removeMethodHelper(
+            method, virtualMethods, newVirtualMethods -> virtualMethods = newVirtualMethods);
+    assert removedVirtualMethod == null || belongsToVirtualPool(removedVirtualMethod);
+    return removedVirtualMethod;
   }
 
-  @Override
-  void removeDirectMethod(DexMethod method) {
-    int index = -1;
-    for (int i = 0; i < directMethods.length; i++) {
-      if (method.match(directMethods[i])) {
-        index = i;
-        break;
+  private DexEncodedMethod removeMethodHelper(
+      DexMethod method,
+      DexEncodedMethod[] methods,
+      Consumer<DexEncodedMethod[]> newMethodsConsumer) {
+    for (int i = 0; i < methods.length; i++) {
+      if (method.match(methods[i])) {
+        return removeMethodWithIndex(i, methods, newMethodsConsumer);
       }
     }
-    if (index >= 0) {
-      removeDirectMethod(index);
-    }
+    return null;
+  }
+
+  private DexEncodedMethod removeMethodWithIndex(
+      int index, DexEncodedMethod[] methods, Consumer<DexEncodedMethod[]> newMethodsConsumer) {
+    DexEncodedMethod removed = methods[index];
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[methods.length - 1];
+    System.arraycopy(methods, 0, newMethods, 0, index);
+    System.arraycopy(methods, index + 1, newMethods, index, methods.length - index - 1);
+    newMethodsConsumer.accept(newMethods);
+    return removed;
   }
 
   @Override
@@ -243,7 +261,8 @@
       if (method.match(directMethod)) {
         DexEncodedMethod newMethod = replacement.apply(directMethod);
         assert belongsToVirtualPool(newMethod);
-        removeDirectMethod(i);
+        removeMethodWithIndex(
+            i, directMethods, newDirectMethods -> directMethods = newDirectMethods);
         addVirtualMethod(newMethod);
         return newMethod;
       }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 6fecde6..ed2f612 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -181,9 +181,16 @@
     backing.addDirectMethods(methods);
   }
 
-  public void removeDirectMethod(DexMethod method) {
-    resetDirectMethodCaches();
-    backing.removeDirectMethod(method);
+  public void removeMethod(DexMethod method) {
+    DexEncodedMethod removed = backing.removeMethod(method);
+    if (removed != null) {
+      if (backing.belongsToDirectPool(removed)) {
+        resetDirectMethodCaches();
+      } else {
+        assert backing.belongsToVirtualPool(removed);
+        resetVirtualMethodCaches();
+      }
+    }
   }
 
   public void setDirectMethods(DexEncodedMethod[] methods) {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 427f022..913a563 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -75,7 +75,7 @@
 
   // Removal methods.
 
-  abstract void removeDirectMethod(DexMethod method);
+  abstract DexEncodedMethod removeMethod(DexMethod method);
 
   // Replacement/mutation methods.
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index 8afffc6..d03e741 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -186,8 +186,8 @@
   }
 
   @Override
-  void removeDirectMethod(DexMethod method) {
-    methodMap.remove(wrap(method));
+  DexEncodedMethod removeMethod(DexMethod method) {
+    return methodMap.remove(wrap(method));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index 3d1c2b5..ffd23cc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -157,9 +158,13 @@
 
   public static DexType getRefinedReceiverType(
       AppView<? extends AppInfoWithSubtyping> appView, InvokeMethodWithReceiver invoke) {
-    Value receiver = invoke.getReceiver();
+    return getRefinedReceiverType(appView, invoke.getInvokedMethod(), invoke.getReceiver());
+  }
+
+  public static DexType getRefinedReceiverType(
+      AppView<? extends AppInfoWithSubtyping> appView, DexMethod method, Value receiver) {
     TypeElement lattice = receiver.getDynamicUpperBoundType(appView);
-    DexType staticReceiverType = invoke.getInvokedMethod().holder;
+    DexType staticReceiverType = method.holder;
     if (lattice.isClassType()) {
       ClassTypeElement classType = lattice.asClassType();
       DexType refinedType = classType.getClassType();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 53573ac..268c065 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -43,6 +44,7 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -715,22 +717,39 @@
   }
 
   private boolean consistentPredecessorSuccessors() {
+    Set<BasicBlock> blockSet = SetUtils.newIdentityHashSet(blocks);
+    Map<BasicBlock, Collection<BasicBlock>> predecessorCollections =
+        new IdentityHashMap<>(blocks.size());
+    Map<BasicBlock, Collection<BasicBlock>> successorCollections =
+        new IdentityHashMap<>(blocks.size());
+    Function<Collection<BasicBlock>, Collection<BasicBlock>> optimizeForMembershipQueries =
+        collection -> collection.size() > 5 ? SetUtils.newIdentityHashSet(collection) : collection;
     for (BasicBlock block : blocks) {
-      // Check that all successors are distinct.
-      assert new HashSet<>(block.getSuccessors()).size() == block.getSuccessors().size();
-      for (BasicBlock succ : block.getSuccessors()) {
-        // Check that successors are in the block list.
-        assert blocks.contains(succ);
-        // Check that successors have this block as a predecessor.
-        assert succ.getPredecessors().contains(block);
+      Collection<BasicBlock> predecessors =
+          predecessorCollections.computeIfAbsent(
+              block, key -> optimizeForMembershipQueries.apply(key.getPredecessors()));
+      Collection<BasicBlock> successors =
+          successorCollections.computeIfAbsent(
+              block, key -> optimizeForMembershipQueries.apply(key.getSuccessors()));
+      // Check that all predecessors and successors are distinct.
+      assert predecessors.size() == block.getPredecessors().size();
+      assert successors.size() == block.getSuccessors().size();
+      // Check that predecessors and successors are in the block list.
+      assert blockSet.containsAll(predecessors);
+      assert blockSet.containsAll(successors);
+      // Check that successors have this block as a predecessor.
+      for (BasicBlock succ : successors) {
+        Collection<BasicBlock> succPredecessors =
+            predecessorCollections.computeIfAbsent(
+                succ, key -> optimizeForMembershipQueries.apply(key.getPredecessors()));
+        assert succPredecessors.contains(block);
       }
-      // Check that all predecessors are distinct.
-      assert new HashSet<>(block.getPredecessors()).size() == block.getPredecessors().size();
-      for (BasicBlock pred : block.getPredecessors()) {
-        // Check that predecessors are in the block list.
-        assert blocks.contains(pred);
-        // Check that predecessors have this block as a successor.
-        assert pred.getSuccessors().contains(block);
+      // Check that predecessors have this block as a successor.
+      for (BasicBlock pred : predecessors) {
+        Collection<BasicBlock> predSuccessors =
+            successorCollections.computeIfAbsent(
+                pred, key -> optimizeForMembershipQueries.apply(key.getSuccessors()));
+        assert predSuccessors.contains(block);
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index c30a8cb..92eb384 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -91,26 +91,31 @@
 
   @Override
   public DexEncodedMethod lookupSingleTarget(AppView<?> appView, DexType invocationContext) {
+    return lookupSingleTarget(appView, invocationContext, getInvokedMethod(), getReceiver());
+  }
+
+  public static DexEncodedMethod lookupSingleTarget(
+      AppView<?> appView, DexType invocationContext, DexMethod method, Value receiver) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       return appViewWithLiveness
           .appInfo()
           .lookupSingleVirtualTarget(
-              getInvokedMethod(),
+              method,
               invocationContext,
               false,
               appView,
-              TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this),
-              getReceiver().getDynamicLowerBoundType(appViewWithLiveness));
+              TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, method, receiver),
+              receiver.getDynamicLowerBoundType(appViewWithLiveness));
     }
     // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is used
     // for library modeling.
-    DexType holder = getInvokedMethod().holder;
+    DexType holder = method.holder;
     if (holder.isClassType()) {
       DexClass clazz = appView.definitionFor(holder);
       if (clazz != null
           && (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) {
-        DexEncodedMethod singleTargetCandidate = appView.definitionFor(getInvokedMethod());
+        DexEncodedMethod singleTargetCandidate = appView.definitionFor(method);
         if (singleTargetCandidate != null && (clazz.isFinal() || singleTargetCandidate.isFinal())) {
           return singleTargetCandidate;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 57c923c..ca2cdd4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -440,6 +440,10 @@
     return uniqueUsers = ImmutableSet.copyOf(users);
   }
 
+  public boolean hasSingleUniqueUser() {
+    return uniqueUsers().size() == 1;
+  }
+
   public Instruction singleUniqueUser() {
     assert ImmutableSet.copyOf(users).size() == 1;
     return users.getFirst();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 1b78ff7..6550178 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
@@ -725,7 +726,8 @@
     }
     if (enumUnboxer != null) {
       enumUnboxer.finishAnalysis();
-      enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
+      enumUnboxer.unboxEnums(
+          postMethodProcessorBuilder, executorService, feedback, classStaticizer);
     }
     new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
         .run(executorService, feedback, timing);
@@ -1122,6 +1124,9 @@
   private Timing optimize(
       IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     DexEncodedMethod method = code.method;
+    DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(method));
+    assert holder != null;
+
     Timing timing = Timing.create(method.qualifiedName(), options);
 
     if (Log.ENABLED) {
@@ -1280,7 +1285,7 @@
     if (devirtualizer != null) {
       assert code.verifyTypes(appView);
       timing.begin("Devirtualize invoke interface");
-      devirtualizer.devirtualizeInvokeInterface(code, method.holder());
+      devirtualizer.devirtualizeInvokeInterface(code, holder);
       timing.end();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 8982b5a..7211439 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
@@ -55,6 +56,8 @@
 
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
 
+  void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo);
+
   void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility);
 
   void setInstanceInitializerInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 85b98ba..4236e52 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -174,7 +174,7 @@
   /** Remove lambda deserialization methods. */
   public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
     for (DexProgramClass clazz : classes) {
-      clazz.removeDirectMethod(getFactory().deserializeLambdaMethod);
+      clazz.removeMethod(getFactory().deserializeLambdaMethod);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index 68453b8..81b43ef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -345,7 +345,8 @@
         }
       } else if (current.isStaticPut()) {
         StaticPut staticPut = current.asStaticPut();
-        if (staticPut.getField().name == dexItemFactory.assertionsDisabled) {
+        if (isInitializerEnablingJavaVmAssertions
+            && staticPut.getField().name == dexItemFactory.assertionsDisabled) {
           iterator.remove();
         }
       } else if (current.isStaticGet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 897b85b..0366798 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -19,8 +20,8 @@
 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.Invoke;
 import com.android.tools.r8.ir.code.InvokeInterface;
+import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -51,7 +52,7 @@
     this.appView = appView;
   }
 
-  public void devirtualizeInvokeInterface(IRCode code, DexType invocationContext) {
+  public void devirtualizeInvokeInterface(IRCode code, DexProgramClass context) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>();
     DominatorTree dominatorTree = new DominatorTree(code);
@@ -114,25 +115,43 @@
           }
         }
 
+        if (appView.options().testing.enableInvokeSuperToInvokeVirtualRewriting) {
+          if (current.isInvokeSuper()) {
+            InvokeSuper invoke = current.asInvokeSuper();
+            DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context.type);
+            if (singleTarget != null) {
+              DexClass holder = appView.definitionForHolder(singleTarget);
+              assert holder != null;
+              DexMethod invokedMethod = invoke.getInvokedMethod();
+              DexEncodedMethod newSingleTarget =
+                  InvokeVirtual.lookupSingleTarget(
+                      appView, context.type, invokedMethod, invoke.getReceiver());
+              if (newSingleTarget == singleTarget) {
+                it.replaceCurrentInstruction(
+                    new InvokeVirtual(invokedMethod, invoke.outValue(), invoke.arguments()));
+              }
+            }
+            continue;
+          }
+        }
+
         if (current.isInvokeVirtual()) {
           InvokeVirtual invoke = current.asInvokeVirtual();
           DexMethod invokedMethod = invoke.getInvokedMethod();
           DexMethod reboundTarget =
-              rebindVirtualInvokeToMostSpecific(
-                  invokedMethod, invoke.getReceiver(), invocationContext);
+              rebindVirtualInvokeToMostSpecific(invokedMethod, invoke.getReceiver(), context.type);
           if (reboundTarget != invokedMethod) {
-            Invoke newInvoke =
-                Invoke.create(
-                    Invoke.Type.VIRTUAL, reboundTarget, null, invoke.outValue(), invoke.inValues());
-            it.replaceCurrentInstruction(newInvoke);
+            it.replaceCurrentInstruction(
+                new InvokeVirtual(reboundTarget, invoke.outValue(), invoke.arguments()));
           }
+          continue;
         }
 
         if (!current.isInvokeInterface()) {
           continue;
         }
         InvokeInterface invoke = current.asInvokeInterface();
-        DexEncodedMethod target = invoke.lookupSingleTarget(appView, invocationContext);
+        DexEncodedMethod target = invoke.lookupSingleTarget(appView, context.type);
         if (target == null) {
           continue;
         }
@@ -144,7 +163,7 @@
         }
         // Due to the potential downcast below, make sure the new target holder is visible.
         ConstraintWithTarget visibility =
-            ConstraintWithTarget.classIsVisible(invocationContext, holderType, appView);
+            ConstraintWithTarget.classIsVisible(context.type, holderType, appView);
         if (visibility == ConstraintWithTarget.NEVER) {
           continue;
         }
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 40f9b7f..d579a34 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
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.google.common.base.Predicates.not;
 
+import com.android.tools.r8.androidapi.AvailableApiExceptions;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -87,6 +88,8 @@
   private final Set<DexEncodedMethod> doubleInlineSelectedTargets = Sets.newIdentityHashSet();
   private final Map<DexEncodedMethod, DexEncodedMethod> doubleInlineeCandidates = new HashMap<>();
 
+  private final AvailableApiExceptions availableApiExceptions;
+
   public Inliner(
       AppView<AppInfoWithLiveness> appView,
       MainDexClasses mainDexClasses,
@@ -101,6 +104,10 @@
     this.lambdaMerger = lambdaMerger;
     this.lensCodeRewriter = lensCodeRewriter;
     this.mainDexClasses = mainDexClasses;
+    availableApiExceptions =
+        appView.options().canHaveDalvikCatchHandlerVerificationBug()
+            ? new AvailableApiExceptions(appView.options())
+            : null;
   }
 
   boolean isBlacklisted(
@@ -145,8 +152,7 @@
   }
 
   public ConstraintWithTarget computeInliningConstraint(IRCode code, DexEncodedMethod method) {
-    if (appView.options().canHaveDalvikCatchHandlerVerificationBug()
-        && useReflectiveOperationExceptionOrUnknownClassInCatch(code)) {
+    if (containsPotentialCatchHandlerVerificationError(code)) {
       return ConstraintWithTarget.NEVER;
     }
 
@@ -1076,13 +1082,16 @@
     assert code.isConsistentSSA();
   }
 
-  private boolean useReflectiveOperationExceptionOrUnknownClassInCatch(IRCode code) {
+  private boolean containsPotentialCatchHandlerVerificationError(IRCode code) {
+    if (availableApiExceptions == null) {
+      assert !appView.options().canHaveDalvikCatchHandlerVerificationBug();
+      return false;
+    }
     for (BasicBlock block : code.blocks) {
       for (CatchHandler<BasicBlock> catchHandler : block.getCatchHandlers()) {
-        if (catchHandler.guard == appView.dexItemFactory().reflectiveOperationExceptionType) {
-          return true;
-        }
-        if (appView.definitionFor(catchHandler.guard) == null) {
+        DexClass clazz = appView.definitionFor(catchHandler.guard);
+        if ((clazz == null || clazz.isLibraryClass())
+            && availableApiExceptions.canCauseVerificationError(catchHandler.guard)) {
           return true;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index e25949d..a7e4875 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -34,11 +34,12 @@
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 /**
@@ -47,9 +48,11 @@
  * <p>Simple algorithm that goes through all blocks in one pass in topological order and propagates
  * active field sets across control-flow edges where the target has only one predecessor.
  */
-// TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
 public class RedundantFieldLoadElimination {
 
+  private static final int MAX_CAPACITY = 10000;
+  private static final int MAX_CAPACITY_PER_BLOCK = 50;
+
   private final AppView<?> appView;
   private final DexEncodedMethod method;
   private final IRCode code;
@@ -58,11 +61,11 @@
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
 
   // Maps keeping track of fields that have an already loaded value at basic block entry.
-  private final Map<BasicBlock, State> activeStateAtExit = new IdentityHashMap<>();
+  private final BlockStates activeStates = new BlockStates();
 
   // Maps keeping track of fields with already loaded values for the current block during
   // elimination.
-  private State activeState;
+  private BlockState activeState;
 
   public RedundantFieldLoadElimination(AppView<?> appView, IRCode code) {
     this.appView = appView;
@@ -72,9 +75,7 @@
 
   public static boolean shouldRun(AppView<?> appView, IRCode code) {
     return appView.options().enableRedundantFieldLoadElimination
-        && (code.metadata().mayHaveFieldGet() || code.metadata().mayHaveInitClass())
-        // TODO(b/154064966): Remove workaround.
-        && code.blocks.size() < 20000;
+        && (code.metadata().mayHaveFieldGet() || code.metadata().mayHaveInitClass());
   }
 
   private interface FieldValue {
@@ -162,18 +163,18 @@
     DexType context = method.holder();
     Reference2IntMap<BasicBlock> pendingNormalSuccessors = new Reference2IntOpenHashMap<>();
     for (BasicBlock block : code.blocks) {
-      if (!block.hasUniqueSuccessor()) {
+      if (!block.hasUniqueNormalSuccessor()) {
         pendingNormalSuccessors.put(block, block.numberOfNormalSuccessors());
       }
     }
 
-    Set<BasicBlock> visited = Sets.newIdentityHashSet();
     for (BasicBlock head : code.topologicallySortedBlocks()) {
-      if (!visited.add(head)) {
+      if (head.hasUniquePredecessor() && head.getUniquePredecessor().hasUniqueNormalSuccessor()) {
+        // Already visited.
         continue;
       }
-      computeActiveStateOnBlockEntry(head);
-      removeDeadBlockExitStates(head, pendingNormalSuccessors);
+      activeState = activeStates.computeActiveStateOnBlockEntry(head);
+      activeStates.removeDeadBlockExitStates(head, pendingNormalSuccessors);
       BasicBlock block = head;
       BasicBlock end = null;
       do {
@@ -314,15 +315,13 @@
         }
         if (block.hasUniqueNormalSuccessorWithUniquePredecessor()) {
           block = block.getUniqueNormalSuccessor();
-          assert !visited.contains(block);
-          visited.add(block);
         } else {
           end = block;
           block = null;
         }
       } while (block != null);
       assert end != null;
-      recordActiveStateOnBlockExit(end);
+      activeStates.recordActiveStateOnBlockExit(end, activeState);
     }
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
@@ -386,66 +385,6 @@
         });
   }
 
-  private void computeActiveStateOnBlockEntry(BasicBlock block) {
-    if (block.isEntry()) {
-      activeState = new State();
-      return;
-    }
-    Deque<State> predecessorExitStates = new ArrayDeque<>(block.getPredecessors().size());
-    for (BasicBlock predecessor : block.getPredecessors()) {
-      State predecessorExitState = activeStateAtExit.get(predecessor);
-      if (predecessorExitState == null) {
-        // Not processed yet.
-        activeState = new State();
-        return;
-      }
-      // Allow propagation across exceptional edges, just be careful not to propagate if the
-      // throwing instruction is a field instruction.
-      if (predecessor.hasCatchSuccessor(block)) {
-        Instruction exceptionalExit = predecessor.exceptionalExit();
-        if (exceptionalExit != null) {
-          predecessorExitState = new State(predecessorExitState);
-          if (exceptionalExit.isFieldInstruction()) {
-            predecessorExitState.killActiveFieldsForExceptionalExit(
-                exceptionalExit.asFieldInstruction());
-          } else if (exceptionalExit.isInitClass()) {
-            predecessorExitState.killActiveInitializedClassesForExceptionalExit(
-                exceptionalExit.asInitClass());
-          }
-        }
-      }
-      predecessorExitStates.addLast(predecessorExitState);
-    }
-    State state = new State(predecessorExitStates.removeFirst());
-    predecessorExitStates.forEach(state::intersect);
-    activeState = state;
-  }
-
-  private void removeDeadBlockExitStates(
-      BasicBlock current, Reference2IntMap<BasicBlock> pendingNormalSuccessorsMap) {
-    for (BasicBlock predecessor : current.getPredecessors()) {
-      if (predecessor.hasUniqueSuccessor()) {
-        activeStateAtExit.remove(predecessor);
-      } else {
-        if (predecessor.hasNormalSuccessor(current)) {
-          int pendingNormalSuccessors = pendingNormalSuccessorsMap.getInt(predecessor) - 1;
-          if (pendingNormalSuccessors == 0) {
-            activeStateAtExit.remove(predecessor);
-          } else {
-            pendingNormalSuccessorsMap.put(predecessor, pendingNormalSuccessors);
-          }
-        }
-      }
-    }
-  }
-
-  private void recordActiveStateOnBlockExit(BasicBlock block) {
-    assert !activeStateAtExit.containsKey(block);
-    if (!activeState.isEmpty()) {
-      activeStateAtExit.put(block, activeState);
-    }
-  }
-
   private void killAllNonFinalActiveFields() {
     activeState.clearNonFinalInstanceFields();
     activeState.clearNonFinalStaticFields();
@@ -476,41 +415,162 @@
     }
   }
 
-  static class State {
+  static class BlockStates {
 
-    private Map<FieldAndObject, FieldValue> finalInstanceFieldValues;
+    // Maps keeping track of fields that have an already loaded value at basic block entry.
+    private final LinkedHashMap<BasicBlock, BlockState> activeStateAtExit = new LinkedHashMap<>();
 
-    private Map<DexField, FieldValue> finalStaticFieldValues;
+    private int capacity = MAX_CAPACITY;
 
-    private Set<DexType> initializedClasses;
-
-    private Map<FieldAndObject, FieldValue> nonFinalInstanceFieldValues;
-
-    private Map<DexField, FieldValue> nonFinalStaticFieldValues;
-
-    public State() {}
-
-    public State(State state) {
-      if (state.finalInstanceFieldValues != null && !state.finalInstanceFieldValues.isEmpty()) {
-        finalInstanceFieldValues = new HashMap<>();
-        finalInstanceFieldValues.putAll(state.finalInstanceFieldValues);
+    BlockState computeActiveStateOnBlockEntry(BasicBlock block) {
+      if (block.isEntry()) {
+        return new BlockState();
       }
-      if (state.finalStaticFieldValues != null && !state.finalStaticFieldValues.isEmpty()) {
-        finalStaticFieldValues = new IdentityHashMap<>();
-        finalStaticFieldValues.putAll(state.finalStaticFieldValues);
+      List<BasicBlock> predecessors = block.getPredecessors();
+      Iterator<BasicBlock> predecessorIterator = predecessors.iterator();
+      BlockState state = new BlockState(activeStateAtExit.get(predecessorIterator.next()));
+      while (predecessorIterator.hasNext()) {
+        BasicBlock predecessor = predecessorIterator.next();
+        BlockState predecessorExitState = activeStateAtExit.get(predecessor);
+        if (predecessorExitState == null) {
+          // Not processed yet.
+          return new BlockState();
+        }
+        state.intersect(predecessorExitState);
       }
-      if (state.initializedClasses != null && !state.initializedClasses.isEmpty()) {
-        initializedClasses = Sets.newIdentityHashSet();
-        initializedClasses.addAll(state.initializedClasses);
+      // Allow propagation across exceptional edges, just be careful not to propagate if the
+      // throwing instruction is a field instruction.
+      for (BasicBlock predecessor : predecessors) {
+        if (predecessor.hasCatchSuccessor(block)) {
+          Instruction exceptionalExit = predecessor.exceptionalExit();
+          if (exceptionalExit != null) {
+            if (exceptionalExit.isFieldInstruction()) {
+              state.killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
+            } else if (exceptionalExit.isInitClass()) {
+              state.killActiveInitializedClassesForExceptionalExit(exceptionalExit.asInitClass());
+            }
+          }
+        }
       }
-      if (state.nonFinalInstanceFieldValues != null
-          && !state.nonFinalInstanceFieldValues.isEmpty()) {
-        nonFinalInstanceFieldValues = new HashMap<>();
-        nonFinalInstanceFieldValues.putAll(state.nonFinalInstanceFieldValues);
+      return state;
+    }
+
+    private void ensureCapacity(BlockState state) {
+      int stateSize = state.size();
+      assert stateSize <= MAX_CAPACITY_PER_BLOCK;
+      int numberOfItemsToRemove = stateSize - capacity;
+      if (numberOfItemsToRemove <= 0) {
+        return;
       }
-      if (state.nonFinalStaticFieldValues != null && !state.nonFinalStaticFieldValues.isEmpty()) {
-        nonFinalStaticFieldValues = new IdentityHashMap<>();
-        nonFinalStaticFieldValues.putAll(state.nonFinalStaticFieldValues);
+      Iterator<Entry<BasicBlock, BlockState>> iterator = activeStateAtExit.entrySet().iterator();
+      while (iterator.hasNext() && numberOfItemsToRemove > 0) {
+        Entry<BasicBlock, BlockState> entry = iterator.next();
+        BlockState existingState = entry.getValue();
+        int existingStateSize = existingState.size();
+        assert existingStateSize > 0;
+        if (existingStateSize <= numberOfItemsToRemove) {
+          iterator.remove();
+          capacity += existingStateSize;
+          numberOfItemsToRemove -= existingStateSize;
+        } else {
+          existingState.reduceSize(numberOfItemsToRemove);
+          capacity += numberOfItemsToRemove;
+          numberOfItemsToRemove = 0;
+        }
+      }
+      if (numberOfItemsToRemove > 0) {
+        state.reduceSize(numberOfItemsToRemove);
+      }
+      assert capacity == MAX_CAPACITY - size();
+    }
+
+    void removeDeadBlockExitStates(
+        BasicBlock current, Reference2IntMap<BasicBlock> pendingNormalSuccessorsMap) {
+      for (BasicBlock predecessor : current.getPredecessors()) {
+        if (predecessor.hasUniqueSuccessor()) {
+          removeState(predecessor);
+        } else {
+          if (predecessor.hasNormalSuccessor(current)) {
+            int pendingNormalSuccessors = pendingNormalSuccessorsMap.getInt(predecessor) - 1;
+            if (pendingNormalSuccessors == 0) {
+              pendingNormalSuccessorsMap.removeInt(predecessor);
+              removeState(predecessor);
+            } else {
+              pendingNormalSuccessorsMap.put(predecessor, pendingNormalSuccessors);
+            }
+          }
+        }
+      }
+    }
+
+    void recordActiveStateOnBlockExit(BasicBlock block, BlockState state) {
+      assert !activeStateAtExit.containsKey(block);
+      if (state.isEmpty()) {
+        return;
+      }
+      ensureCapacity(state);
+      activeStateAtExit.put(block, state);
+      capacity -= state.size();
+      assert capacity >= 0;
+    }
+
+    private void removeState(BasicBlock block) {
+      BlockState state = activeStateAtExit.remove(block);
+      if (state != null) {
+        int stateSize = state.size();
+        assert stateSize > 0;
+        capacity += stateSize;
+      }
+    }
+
+    private int size() {
+      int size = 0;
+      for (BlockState state : activeStateAtExit.values()) {
+        int stateSize = state.size();
+        assert stateSize > 0;
+        size += stateSize;
+      }
+      return size;
+    }
+  }
+
+  static class BlockState {
+
+    private LinkedHashMap<FieldAndObject, FieldValue> finalInstanceFieldValues;
+
+    private LinkedHashMap<DexField, FieldValue> finalStaticFieldValues;
+
+    private LinkedHashSet<DexType> initializedClasses;
+
+    private LinkedHashMap<FieldAndObject, FieldValue> nonFinalInstanceFieldValues;
+
+    private LinkedHashMap<DexField, FieldValue> nonFinalStaticFieldValues;
+
+    public BlockState() {}
+
+    public BlockState(BlockState state) {
+      if (state != null) {
+        if (state.finalInstanceFieldValues != null && !state.finalInstanceFieldValues.isEmpty()) {
+          finalInstanceFieldValues = new LinkedHashMap<>();
+          finalInstanceFieldValues.putAll(state.finalInstanceFieldValues);
+        }
+        if (state.finalStaticFieldValues != null && !state.finalStaticFieldValues.isEmpty()) {
+          finalStaticFieldValues = new LinkedHashMap<>();
+          finalStaticFieldValues.putAll(state.finalStaticFieldValues);
+        }
+        if (state.initializedClasses != null && !state.initializedClasses.isEmpty()) {
+          initializedClasses = new LinkedHashSet<>();
+          initializedClasses.addAll(state.initializedClasses);
+        }
+        if (state.nonFinalInstanceFieldValues != null
+            && !state.nonFinalInstanceFieldValues.isEmpty()) {
+          nonFinalInstanceFieldValues = new LinkedHashMap<>();
+          nonFinalInstanceFieldValues.putAll(state.nonFinalInstanceFieldValues);
+        }
+        if (state.nonFinalStaticFieldValues != null && !state.nonFinalStaticFieldValues.isEmpty()) {
+          nonFinalStaticFieldValues = new LinkedHashMap<>();
+          nonFinalStaticFieldValues.putAll(state.nonFinalStaticFieldValues);
+        }
       }
     }
 
@@ -522,6 +582,14 @@
       nonFinalStaticFieldValues = null;
     }
 
+    public void ensureCapacityForNewElement() {
+      int size = size();
+      assert size <= MAX_CAPACITY_PER_BLOCK;
+      if (size == MAX_CAPACITY_PER_BLOCK) {
+        reduceSize(1);
+      }
+    }
+
     public FieldValue getInstanceFieldValue(FieldAndObject field) {
       FieldValue value =
           nonFinalInstanceFieldValues != null ? nonFinalInstanceFieldValues.get(field) : null;
@@ -540,7 +608,7 @@
       return finalStaticFieldValues != null ? finalStaticFieldValues.get(field) : null;
     }
 
-    public void intersect(State state) {
+    public void intersect(BlockState state) {
       if (finalInstanceFieldValues != null && state.finalInstanceFieldValues != null) {
         intersectFieldValues(finalInstanceFieldValues, state.finalInstanceFieldValues);
       } else {
@@ -619,12 +687,41 @@
     }
 
     public void markClassAsInitialized(DexType clazz) {
+      ensureCapacityForNewElement();
       if (initializedClasses == null) {
-        initializedClasses = Sets.newIdentityHashSet();
+        initializedClasses = new LinkedHashSet<>();
       }
       initializedClasses.add(clazz);
     }
 
+    public void reduceSize(int numberOfItemsToRemove) {
+      assert numberOfItemsToRemove > 0;
+      assert numberOfItemsToRemove < size();
+      numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, initializedClasses);
+      numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, nonFinalInstanceFieldValues);
+      numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, nonFinalStaticFieldValues);
+      numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, finalInstanceFieldValues);
+      numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, finalStaticFieldValues);
+      assert numberOfItemsToRemove == 0;
+    }
+
+    private static int reduceSize(int numberOfItemsToRemove, Set<?> set) {
+      if (set == null || numberOfItemsToRemove == 0) {
+        return numberOfItemsToRemove;
+      }
+      Iterator<?> iterator = set.iterator();
+      while (iterator.hasNext() && numberOfItemsToRemove > 0) {
+        iterator.next();
+        iterator.remove();
+        numberOfItemsToRemove--;
+      }
+      return numberOfItemsToRemove;
+    }
+
+    private static int reduceSize(int numberOfItemsToRemove, Map<?, ?> map) {
+      return reduceSize(numberOfItemsToRemove, map != null ? map.keySet() : null);
+    }
+
     public void removeInstanceField(FieldAndObject field) {
       removeFinalInstanceField(field);
       removeNonFinalInstanceField(field);
@@ -666,33 +763,53 @@
     }
 
     public void putFinalInstanceField(FieldAndObject field, FieldValue value) {
+      ensureCapacityForNewElement();
       if (finalInstanceFieldValues == null) {
-        finalInstanceFieldValues = new HashMap<>();
+        finalInstanceFieldValues = new LinkedHashMap<>();
       }
       finalInstanceFieldValues.put(field, value);
     }
 
     public void putFinalStaticField(DexField field, FieldValue value) {
+      ensureCapacityForNewElement();
       if (finalStaticFieldValues == null) {
-        finalStaticFieldValues = new IdentityHashMap<>();
+        finalStaticFieldValues = new LinkedHashMap<>();
       }
       finalStaticFieldValues.put(field, value);
     }
 
     public void putNonFinalInstanceField(FieldAndObject field, FieldValue value) {
+      ensureCapacityForNewElement();
       assert finalInstanceFieldValues == null || !finalInstanceFieldValues.containsKey(field);
       if (nonFinalInstanceFieldValues == null) {
-        nonFinalInstanceFieldValues = new HashMap<>();
+        nonFinalInstanceFieldValues = new LinkedHashMap<>();
       }
       nonFinalInstanceFieldValues.put(field, value);
     }
 
     public void putNonFinalStaticField(DexField field, FieldValue value) {
+      ensureCapacityForNewElement();
       assert nonFinalStaticFieldValues == null || !nonFinalStaticFieldValues.containsKey(field);
       if (nonFinalStaticFieldValues == null) {
-        nonFinalStaticFieldValues = new IdentityHashMap<>();
+        nonFinalStaticFieldValues = new LinkedHashMap<>();
       }
       nonFinalStaticFieldValues.put(field, value);
     }
+
+    public int size() {
+      return size(finalInstanceFieldValues)
+          + size(finalStaticFieldValues)
+          + size(initializedClasses)
+          + size(nonFinalInstanceFieldValues)
+          + size(nonFinalStaticFieldValues);
+    }
+
+    private static int size(Set<?> set) {
+      return set != null ? set.size() : 0;
+    }
+
+    private static int size(Map<?, ?> map) {
+      return map != null ? map.size() : 0;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 6a91188..0a35a6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -283,7 +284,8 @@
   public void unboxEnums(
       PostMethodProcessor.Builder postBuilder,
       ExecutorService executorService,
-      OptimizationFeedbackDelayed feedback)
+      OptimizationFeedbackDelayed feedback,
+      ClassStaticizer classStaticizer)
       throws ExecutionException {
     // At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
     if (enumsUnboxingCandidates.isEmpty()) {
@@ -299,7 +301,7 @@
           appView
               .appInfo()
               .rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
-
+      classStaticizer.filterCandidates();
       // Update optimization info.
       feedback.fixupOptimizationInfos(
           appView,
@@ -659,7 +661,21 @@
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         if (enumsToUnbox.contains(clazz.type)) {
           assert clazz.instanceFields().size() == 0;
-          clearEnumToUnboxMethods(clazz);
+          // TODO(b/150370354): Remove when static methods are supported.
+          if (appView.options().testing.enumUnboxingRewriteJavaCGeneratedMethod) {
+            // Clear only the initializers.
+            clazz
+                .methods()
+                .forEach(
+                    m -> {
+                      if (m.isInitializer()) {
+                        clearEnumToUnboxMethod(m);
+                      }
+                    });
+            clazz.getMethodCollection().replaceMethods(this::fixupMethod);
+          } else {
+            clazz.methods().forEach(this::clearEnumToUnboxMethod);
+          }
         } else {
           clazz.getMethodCollection().replaceMethods(this::fixupMethod);
           fixupFields(clazz.staticFields(), clazz::setStaticField);
@@ -672,19 +688,17 @@
       return lensBuilder.build(factory, appView.graphLense());
     }
 
-    private void clearEnumToUnboxMethods(DexProgramClass clazz) {
+    private void clearEnumToUnboxMethod(DexEncodedMethod enumMethod) {
       // The compiler may have references to the enum methods, but such methods will be removed
       // and they cannot be reprocessed since their rewriting through the lensCodeRewriter/
       // enumUnboxerRewriter will generate invalid code.
       // To work around this problem we clear such methods, i.e., we replace the code object by
       // an empty throwing code object, so reprocessing won't take time and will be valid.
-      for (DexEncodedMethod method : clazz.methods()) {
-        method.setCode(
-            appView.options().isGeneratingClassFiles()
-                ? method.buildEmptyThrowingCfCode()
-                : method.buildEmptyThrowingDexCode(),
-            appView);
-      }
+      enumMethod.setCode(
+          appView.options().isGeneratingClassFiles()
+              ? enumMethod.buildEmptyThrowingCfCode()
+              : enumMethod.buildEmptyThrowingDexCode(),
+          appView);
     }
 
     private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 6ba4154..7b91c7e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -12,6 +14,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -20,6 +23,7 @@
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
@@ -38,8 +42,10 @@
 import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -51,17 +57,22 @@
 public class EnumUnboxingRewriter {
 
   public static final String ENUM_UNBOXING_UTILITY_CLASS_NAME = "$r8$EnumUnboxingUtility";
+  public static final String ENUM_UNBOXING_UTILITY_ORDINAL = "$enumboxing$ordinal";
+  private static final String ENUM_UNBOXING_UTILITY_VALUES = "$enumboxing$values";
   private static final int REQUIRED_CLASS_FILE_VERSION = 52;
 
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final EnumValueInfoMapCollection enumsToUnbox;
-  private final Map<DexMethod, DexType> extraMethods = new ConcurrentHashMap<>();
+  private final Map<DexMethod, DexEncodedMethod> extraUtilityMethods = new ConcurrentHashMap<>();
+  private final Map<DexField, DexEncodedField> extraUtilityFields = new ConcurrentHashMap<>();
 
   private final DexType utilityClassType;
   private final DexMethod ordinalUtilityMethod;
+  private final DexMethod valuesUtilityMethod;
 
   private boolean requiresOrdinalUtilityMethod = false;
+  private boolean requiresValuesUtilityMethod = false;
 
   EnumUnboxingRewriter(AppView<AppInfoWithLiveness> appView, Set<DexType> enumsToUnbox) {
     this.appView = appView;
@@ -77,7 +88,12 @@
         factory.createMethod(
             utilityClassType,
             factory.createProto(factory.intType, factory.intType),
-            "$enumboxing$ordinal");
+            ENUM_UNBOXING_UTILITY_ORDINAL);
+    this.valuesUtilityMethod =
+        factory.createMethod(
+            utilityClassType,
+            factory.createProto(factory.intArrayType, factory.intType),
+            ENUM_UNBOXING_UTILITY_VALUES);
   }
 
   public EnumValueInfoMapCollection getEnumsToUnbox() {
@@ -146,12 +162,28 @@
           }
           EnumValueInfoMap enumValueInfoMap = enumsToUnbox.getEnumValueInfoMap(holder);
           assert enumValueInfoMap != null;
-          // Replace by ordinal + 1 for null check (null is 0).
-          EnumValueInfo enumValueInfo = enumValueInfoMap.getEnumValueInfo(staticGet.getField());
-          assert enumValueInfo != null
-              : "Invalid read to " + staticGet.getField().name + ", error during enum analysis";
           affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
-          iterator.replaceCurrentInstruction(code.createIntConstant(enumValueInfo.convertToInt()));
+          EnumValueInfo enumValueInfo = enumValueInfoMap.getEnumValueInfo(staticGet.getField());
+          if (enumValueInfo == null && staticGet.getField().name == factory.enumValuesFieldName) {
+            requiresValuesUtilityMethod = true;
+            DexField fieldValues = createValuesField(holder);
+            extraUtilityFields.computeIfAbsent(fieldValues, this::computeValuesEncodedField);
+            DexMethod methodValues = createValuesMethod(holder);
+            extraUtilityMethods.computeIfAbsent(
+                methodValues,
+                m -> computeValuesEncodedMethod(m, fieldValues, enumValueInfoMap.size()));
+            Value rewrittenOutValue =
+                code.createValue(
+                    ArrayTypeElement.create(TypeElement.getInt(), definitelyNotNull()));
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(methodValues, rewrittenOutValue, ImmutableList.of()));
+          } else {
+            // Replace by ordinal + 1 for null check (null is 0).
+            assert enumValueInfo != null
+                : "Invalid read to " + staticGet.getField().name + ", error during enum analysis";
+            iterator.replaceCurrentInstruction(
+                code.createIntConstant(enumValueInfo.convertToInt()));
+          }
         }
       }
       // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
@@ -186,19 +218,59 @@
     return enumsToUnbox.containsEnum(type.asClassType().getClassType());
   }
 
+  private String compatibleName(DexType type) {
+    return type.toSourceString().replace('.', '$');
+  }
+
+  private DexField createValuesField(DexType type) {
+    return factory.createField(
+        utilityClassType,
+        factory.intArrayType,
+        factory.enumValuesFieldName + "$field$" + compatibleName(type));
+  }
+
+  private DexEncodedField computeValuesEncodedField(DexField field) {
+    return new DexEncodedField(
+        field,
+        FieldAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC),
+        DexAnnotationSet.empty(),
+        null);
+  }
+
+  private DexMethod createValuesMethod(DexType type) {
+    return factory.createMethod(
+        utilityClassType,
+        factory.createProto(factory.intArrayType),
+        factory.enumValuesFieldName + "$method$" + compatibleName(type));
+  }
+
+  private DexEncodedMethod computeValuesEncodedMethod(
+      DexMethod method, DexField fieldValues, int numEnumInstances) {
+    CfCode cfCode =
+        new EnumUnboxingCfCodeProvider.EnumUnboxingValuesCfCodeProvider(
+                appView, utilityClassType, fieldValues, numEnumInstances, valuesUtilityMethod)
+            .generateCfCode();
+    return synthesizeUtilityMethod(cfCode, method, true);
+  }
+
   private DexMethod computeValueOfUtilityMethod(DexType type) {
     assert enumsToUnbox.containsEnum(type);
     DexMethod valueOf =
         factory.createMethod(
             utilityClassType,
             factory.createProto(factory.intType, factory.stringType),
-            "valueOf" + type.toSourceString().replace('.', '$'));
-    extraMethods.putIfAbsent(valueOf, type);
+            "valueOf" + compatibleName(type));
+    extraUtilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, type));
     return valueOf;
   }
 
   private boolean shouldRewriteArrayAccess(ArrayAccess arrayAccess) {
     ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
+    if (arrayType == null) {
+      assert arrayAccess.array().getType().isNullType();
+      return false;
+    }
     if (arrayType.getNesting() != 1) {
       return false;
     }
@@ -213,19 +285,21 @@
       throws ExecutionException {
     // Synthesize a class which holds various utility methods that may be called from the IR
     // rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
-    List<DexEncodedMethod> requiredMethods = new ArrayList<>();
-    extraMethods.forEach(
-        (method, enumType) -> {
-          requiredMethods.add(synthesizeValueOfUtilityMethod(method, enumType));
-        });
+    List<DexEncodedMethod> requiredMethods = new ArrayList<>(extraUtilityMethods.values());
+    // Sort for deterministic order.
     requiredMethods.sort((m1, m2) -> m1.method.name.slowCompareTo(m2.method.name));
     if (requiresOrdinalUtilityMethod) {
       requiredMethods.add(synthesizeOrdinalMethod());
     }
+    if (requiresValuesUtilityMethod) {
+      requiredMethods.add(synthesizeValuesUtilityMethod());
+    }
     // TODO(b/147860220): synthesize also other enum methods.
     if (requiredMethods.isEmpty()) {
       return;
     }
+    DexEncodedField[] fields = extraUtilityFields.values().toArray(DexEncodedField.EMPTY_ARRAY);
+    Arrays.sort(fields, (f1, f2) -> f1.field.name.slowCompareTo(f2.field.name));
     DexProgramClass utilityClass =
         new DexProgramClass(
             utilityClassType,
@@ -240,7 +314,7 @@
             null,
             Collections.emptyList(),
             DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
+            fields,
             DexEncodedField.EMPTY_ARRAY,
             // All synthesized methods are static in this case.
             requiredMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
@@ -259,7 +333,7 @@
             .generateCfCode();
     return new DexEncodedMethod(
         method,
-        synthesizedMethodAccessFlags(),
+        synthesizedMethodAccessFlags(false),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         cfCode,
@@ -280,9 +354,19 @@
   private DexEncodedMethod synthesizeOrdinalMethod() {
     CfCode cfCode =
         EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
+    return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod, false);
+  }
+
+  private DexEncodedMethod synthesizeValuesUtilityMethod() {
+    CfCode cfCode =
+        EnumUnboxingCfMethods.EnumUnboxingMethods_values(appView.options(), valuesUtilityMethod);
+    return synthesizeUtilityMethod(cfCode, valuesUtilityMethod, false);
+  }
+
+  private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method, boolean sync) {
     return new DexEncodedMethod(
-        ordinalUtilityMethod,
-        synthesizedMethodAccessFlags(),
+        method,
+        synthesizedMethodAccessFlags(sync),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         cfCode,
@@ -290,8 +374,11 @@
         true);
   }
 
-  private MethodAccessFlags synthesizedMethodAccessFlags() {
-    return MethodAccessFlags.fromSharedAccessFlags(
-        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, false);
+  private MethodAccessFlags synthesizedMethodAccessFlags(boolean sync) {
+    int access = Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC;
+    if (sync) {
+      access = access | Constants.ACC_SYNCHRONIZED;
+    }
+    return MethodAccessFlags.fromSharedAccessFlags(access, false);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 414e55f..59e24a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.google.common.collect.ImmutableSet;
@@ -133,6 +134,11 @@
   }
 
   @Override
+  public BridgeInfo getBridgeInfo() {
+    return null;
+  }
+
+  @Override
   public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
     return UNKNOWN_CLASS_INLINER_ELIGIBILITY;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index c74bc0a..16ce797 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import java.util.BitSet;
 import java.util.Set;
@@ -54,6 +55,8 @@
 
   boolean neverReturnsNormally();
 
+  BridgeInfo getBridgeInfo();
+
   ClassInlinerEligibilityInfo getClassInlinerEligibility();
 
   Set<DexType> getInitializedClassesOnNormalExit();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 4889b2e..d1ddce9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -85,6 +85,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -126,6 +127,7 @@
       DynamicTypeOptimization dynamicTypeOptimization,
       InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos,
       Timing timing) {
+    identifyBridgeInfo(method, code, feedback, timing);
     identifyClassInlinerEligibility(method, code, feedback, timing);
     identifyParameterUsages(method, code, feedback, timing);
     identifyReturnsArgument(method, code, feedback, timing);
@@ -142,6 +144,13 @@
     computeNonNullParamOnNormalExits(feedback, code, timing);
   }
 
+  private void identifyBridgeInfo(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    timing.begin("Identify bridge info");
+    feedback.setBridgeInfo(method, BridgeAnalyzer.analyzeMethod(method, code));
+    timing.end();
+  }
+
   private void identifyClassInlinerEligibility(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
     timing.begin("Identify class inliner eligibility");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 212a044..d4eef20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.AppInfoWithLivenessModifier;
@@ -242,6 +243,11 @@
   }
 
   @Override
+  public void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo) {
+    getMethodOptimizationInfoForUpdating(method).setBridgeInfo(bridgeInfo);
+  }
+
+  @Override
   public synchronized void setClassInlinerEligibility(
       DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
     getMethodOptimizationInfoForUpdating(method).setClassInlinerEligibility(eligibility);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 2f20a39..435f05f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
@@ -107,6 +108,9 @@
   public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
 
   @Override
+  public void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo) {}
+
+  @Override
   public void setClassInlinerEligibility(
       DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {}
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 302a58b..c03c8e0d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
@@ -154,6 +155,11 @@
   }
 
   @Override
+  public void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo) {
+    // Ignored.
+  }
+
+  @Override
   public void setClassInlinerEligibility(
       DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
     // Ignored.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index ed6233f..5ad6c86 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -34,6 +35,7 @@
   private InlinePreference inlining = InlinePreference.Default;
   // Stores information about instance methods and constructors for
   // class inliner, null value indicates that the method is not eligible.
+  private BridgeInfo bridgeInfo = null;
   private ClassInlinerEligibilityInfo classInlinerEligibility =
       DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
   private InstanceInitializerInfo instanceInitializerInfo =
@@ -134,6 +136,7 @@
     returnsObjectWithUpperBoundType = template.returnsObjectWithUpperBoundType;
     returnsObjectWithLowerBoundType = template.returnsObjectWithLowerBoundType;
     inlining = template.inlining;
+    bridgeInfo = template.bridgeInfo;
     classInlinerEligibility = template.classInlinerEligibility;
     instanceInitializerInfo = template.instanceInitializerInfo;
     parametersUsages = template.parametersUsages;
@@ -300,6 +303,15 @@
   }
 
   @Override
+  public BridgeInfo getBridgeInfo() {
+    return bridgeInfo;
+  }
+
+  void setBridgeInfo(BridgeInfo bridgeInfo) {
+    this.bridgeInfo = bridgeInfo;
+  }
+
+  @Override
   public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
     return classInlinerEligibility;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
new file mode 100644
index 0000000..8f28fd5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
@@ -0,0 +1,175 @@
+// 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.info.bridge;
+
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanUtils;
+
+public class BridgeAnalyzer {
+
+  /** Returns a {@link BridgeInfo} object describing this method if it is recognized as a bridge. */
+  public static BridgeInfo analyzeMethod(DexEncodedMethod method, IRCode code) {
+    // TODO(b/154263783): Consider computing BridgeInfo also for non-declared bridges.
+    if (!method.isBridge() || code.blocks.size() > 1) {
+      return failure();
+    }
+
+    // Scan through the instructions one-by-one. We expect a sequence of Argument instructions,
+    // followed by a (possibly empty) sequence of CheckCast instructions, followed by a single
+    // InvokeMethod instruction, followed by an optional CheckCast instruction, followed by a Return
+    // instruction.
+    InvokeVirtual uniqueInvoke = null;
+    CheckCast uniqueReturnCast = null;
+    for (Instruction instruction : code.entryBlock().getInstructions()) {
+      switch (instruction.opcode()) {
+        case ARGUMENT:
+          break;
+
+        case ASSUME:
+          break;
+
+        case CHECK_CAST:
+          {
+            CheckCast checkCast = instruction.asCheckCast();
+            if (!analyzeCheckCast(method, checkCast, uniqueInvoke)) {
+              return failure();
+            }
+            // If we have moved past the single invoke instruction, then record that this cast is
+            // the cast instruction for the result value.
+            if (uniqueInvoke != null) {
+              if (uniqueReturnCast != null) {
+                return failure();
+              }
+              uniqueReturnCast = checkCast;
+            }
+            break;
+          }
+
+        case INVOKE_VIRTUAL:
+          {
+            if (uniqueInvoke != null) {
+              return failure();
+            }
+            InvokeVirtual invoke = instruction.asInvokeVirtual();
+            if (!analyzeInvokeVirtual(invoke)) {
+              return failure();
+            }
+            // Record that we have seen the single invoke instruction.
+            uniqueInvoke = invoke;
+            break;
+          }
+
+        case RETURN:
+          if (!analyzeReturn(instruction.asReturn(), uniqueInvoke, uniqueReturnCast)) {
+            return failure();
+          }
+          break;
+
+        default:
+          return failure();
+      }
+    }
+
+    assert uniqueInvoke != null;
+    return new VirtualBridgeInfo(uniqueInvoke.getInvokedMethod());
+  }
+
+  private static boolean analyzeCheckCast(
+      DexEncodedMethod method, CheckCast checkCast, InvokeMethod invoke) {
+    return invoke == null
+        ? analyzeCheckCastBeforeInvoke(checkCast)
+        : analyzeCheckCastAfterInvoke(method, checkCast, invoke);
+  }
+
+  private static boolean analyzeCheckCastBeforeInvoke(CheckCast checkCast) {
+    Value object = checkCast.object().getAliasedValue();
+    // It must be casting one of the arguments.
+    if (!object.isArgument()) {
+      return false;
+    }
+    int argumentIndex = object.definition.asArgument().getIndex();
+    Value castValue = checkCast.outValue();
+    // The out value cannot have any phi users since there is only one block.
+    assert !castValue.hasPhiUsers();
+    // It is not allowed to have any other users than the invoke instruction.
+    if (castValue.hasDebugUsers() || !castValue.hasSingleUniqueUser()) {
+      return false;
+    }
+    InvokeMethod invoke = castValue.singleUniqueUser().asInvokeMethod();
+    if (invoke == null) {
+      return false;
+    }
+    if (invoke.arguments().size() <= argumentIndex) {
+      return false;
+    }
+    int parameterIndex = argumentIndex - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver());
+    // It is not allowed to cast the receiver.
+    if (parameterIndex == -1) {
+      return false;
+    }
+    // The type of the cast must match the type of the parameter.
+    if (checkCast.getType() != invoke.getInvokedMethod().proto.getParameter(parameterIndex)) {
+      return false;
+    }
+    // It must be forwarded at the same argument index.
+    Value argument = invoke.getArgument(argumentIndex);
+    if (argument != castValue) {
+      return false;
+    }
+    return true;
+  }
+
+  private static boolean analyzeCheckCastAfterInvoke(
+      DexEncodedMethod method, CheckCast checkCast, InvokeMethod invoke) {
+    Value returnValue = invoke.outValue();
+    Value uncastValue = checkCast.object().getAliasedValue();
+    Value castValue = checkCast.outValue();
+    // The out value cannot have any phi users since there is only one block.
+    assert !castValue.hasPhiUsers();
+    // It must cast the result to the return type of the enclosing method and return the cast value.
+    return uncastValue == returnValue
+        && checkCast.getType() == method.returnType()
+        && !castValue.hasDebugUsers()
+        && castValue.hasSingleUniqueUser()
+        && castValue.singleUniqueUser().isReturn();
+  }
+
+  private static boolean analyzeInvokeVirtual(InvokeVirtual invoke) {
+    // All of the forwarded arguments of the enclosing method must be in the same argument position.
+    for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+      Value argument = invoke.getArgument(argumentIndex);
+      if (argument.isArgument() && argumentIndex != argument.definition.asArgument().getIndex()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private static boolean analyzeReturn(Return ret, InvokeMethod invoke, CheckCast returnCast) {
+    // If we haven't seen an invoke this is not a bridge.
+    if (invoke == null) {
+      return false;
+    }
+    // It must not return a value, or the return value must be the result value of the invoke.
+    return ret.isReturnVoid()
+        || ret.returnValue() == (returnCast != null ? returnCast : invoke).outValue();
+  }
+
+  private static BridgeInfo failure() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
new file mode 100644
index 0000000..2683d2f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
@@ -0,0 +1,19 @@
+// 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.info.bridge;
+
+/**
+ * A piece of optimization info that is computed for bridge methods. The info stores details about
+ * the behavior of the bridge.
+ */
+public abstract class BridgeInfo {
+
+  public boolean isVirtualBridgeInfo() {
+    return false;
+  }
+
+  public VirtualBridgeInfo asVirtualBridgeInfo() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
new file mode 100644
index 0000000..a07abdc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
@@ -0,0 +1,49 @@
+// 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.info.bridge;
+
+import com.android.tools.r8.graph.DexMethod;
+
+/**
+ * Optimization info computed for bridge methods that use an invoke-virtual instruction.
+ *
+ * <p>If the method is {@code String A.m(Object)} and {@link #invokedMethod} is {@code Object
+ * B.m(String)}, then the bridge is implemented as:
+ *
+ * <pre>
+ *   java.lang.String A.m(java.lang.Object o) {
+ *     v0 <- Argument
+ *     v1 <- CheckCast v0, java.lang.String
+ *     v2 <- InvokeVirtual { v0, v1 }, java.lang.Object B.m(java.lang.String)
+ *     v3 <- CheckCast v2, java.lang.String
+ *     Return v3
+ *   }
+ * </pre>
+ *
+ * <p>This currently does not allow any permutation of the argument order, and it also does not
+ * allow constants to be passed as arguments.
+ */
+public class VirtualBridgeInfo extends BridgeInfo {
+
+  // The targeted method.
+  private final DexMethod invokedMethod;
+
+  public VirtualBridgeInfo(DexMethod invokedMethod) {
+    this.invokedMethod = invokedMethod;
+  }
+
+  public DexMethod getInvokedMethod() {
+    return invokedMethod;
+  }
+
+  @Override
+  public boolean isVirtualBridgeInfo() {
+    return true;
+  }
+
+  @Override
+  public VirtualBridgeInfo asVirtualBridgeInfo() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 41df56a..ed3e72a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -96,6 +96,15 @@
       candidates.remove(candidate.type);
       return null;
     }
+
+    void filterMethods() {
+      Set<DexEncodedMethod> newReferencedFrom = Sets.newIdentityHashSet();
+      for (DexEncodedMethod dexEncodedMethod : referencedFrom) {
+        newReferencedFrom.add(appView.graphLense().mapDexEncodedMethod(dexEncodedMethod, appView));
+      }
+      referencedFrom.clear();
+      referencedFrom.addAll(newReferencedFrom);
+    }
   }
 
   // The map storing all the potential candidates for staticizing.
@@ -651,6 +660,17 @@
     return false;
   }
 
+  // Methods may have their signature changed in-between the IR processing rounds, leading to
+  // duplicates where one version is the outdated version. Remove these.
+  // This also ensures no unboxed enum are staticized, if that would be the case, then
+  // the candidate would need to be removed from the candidate list.
+  public void filterCandidates() {
+    for (Map.Entry<DexType, CandidateInfo> entry : candidates.entrySet()) {
+      assert !appView.unboxedEnums().containsEnum(entry.getKey());
+      entry.getValue().filterMethods();
+    }
+  }
+
   // Perform staticizing candidates:
   //
   //  1. After filtering candidates based on usage, finalize the list of candidates by
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 1d679ecc..c451bd7 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
@@ -286,12 +286,22 @@
       if (getter != null) {
         singletonGetters.put(getter.method, candidate);
       }
+      assert validMethods(candidate.referencedFrom);
       referencingExtraMethods.addAll(candidate.referencedFrom);
     }
 
     referencingExtraMethods.removeAll(removedInstanceMethods);
   }
 
+  private boolean validMethods(Set<DexEncodedMethod> referencedFrom) {
+    for (DexEncodedMethod dexEncodedMethod : referencedFrom) {
+      DexClass clazz = appView.definitionForHolder(dexEncodedMethod.method);
+      assert clazz != null;
+      assert clazz.lookupMethod(dexEncodedMethod.method) == dexEncodedMethod;
+    }
+    return true;
+  }
+
   private void enqueueMethodsWithCodeOptimizations(
       Iterable<DexEncodedMethod> methods,
       Consumer<ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>> extension) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index b30d567..f9f66e8 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -18,7 +19,9 @@
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
 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.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.ir.code.If;
@@ -104,4 +107,47 @@
       return standardCfCodeFromInstructions(instructions);
     }
   }
+
+  public static class EnumUnboxingValuesCfCodeProvider extends EnumUnboxingCfCodeProvider {
+
+    private final DexField utilityField;
+    private final int numEnumInstances;
+    private final DexMethod initializationMethod;
+
+    public EnumUnboxingValuesCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        DexField utilityField,
+        int numEnumInstances,
+        DexMethod initializationMethod) {
+      super(appView, holder);
+      assert utilityField.type == appView.dexItemFactory().intArrayType;
+      this.utilityField = utilityField;
+      this.numEnumInstances = numEnumInstances;
+      this.initializationMethod = initializationMethod;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      // Generated static method, for class com.x.MyEnum {A,B}, and a field in VALUES$com$x$MyEnum
+      // on Utility class, would look like:
+      // synchronized int[] UtilityClass#com$x$MyEnum_VALUES() {
+      //    if (VALUES$com$x$MyEnum == null) {
+      //      VALUES$com$x$MyEnum = EnumUnboxingMethods_values(2);
+      //    }
+      //    return VALUES$com$x$MyEnum;
+      List<CfInstruction> instructions = new ArrayList<>();
+      CfLabel nullDest = new CfLabel();
+      instructions.add(new CfFieldInstruction(Opcodes.GETSTATIC, utilityField, utilityField));
+      instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
+      instructions.add((new CfConstNumber(numEnumInstances, ValueType.INT)));
+      assert initializationMethod.getArity() == 1;
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, initializationMethod, false));
+      instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, utilityField, utilityField));
+      instructions.add(nullDest);
+      instructions.add(new CfFieldInstruction(Opcodes.GETSTATIC, utilityField, utilityField));
+      instructions.add(new CfReturn(ValueType.OBJECT));
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 609feaa..59fee69 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.naming;
 
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
@@ -16,11 +16,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
-import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
@@ -158,31 +156,49 @@
   }
 
   /**
-   * Checks whether the target is precise enough to be translated,
-   *
-   * <p>We only track the renaming of actual definitions, Thus, if we encounter a method id that
-   * does not directly point at a definition, we won't find the actual renaming. To avoid
-   * dispatching on every lookup, we assume that the tree has been fully dispatched by {@link
-   * MemberRebindingAnalysis}.
-   *
-   * <p>Library methods are excluded from this check, as those are never renamed.
+   * Checks that the renaming of the method reference {@param method} is consistent with the
+   * renaming of the resolution target of {@param method}.
    */
   @Override
-  public boolean checkTargetCanBeTranslated(DexMethod item) {
-    if (item.holder.isArrayType()) {
+  public boolean verifyRenamingConsistentWithResolution(DexMethod method) {
+    if (method.holder.isArrayType()) {
       // Array methods are never renamed, so do not bother to check.
       return true;
     }
-    DexClass holder = appView.definitionFor(item.holder);
-    if (holder == null || holder.isNotProgramClass()) {
+
+    ResolutionResult resolution = appView.appInfo().resolveMethod(method.holder, method);
+    assert resolution != null;
+
+    if (resolution.isSingleResolution()) {
+      // If we can resolve `item`, then the renaming for `item` and its resolution should be the
+      // same.
+      DexEncodedMethod resolvedMethod = resolution.asSingleResolution().getResolvedMethod();
+      assert lookupName(method) == lookupName(resolvedMethod.method);
       return true;
     }
-    SingleResolutionResult resolution =
-        appView.appInfo().resolveMethod(item.holder, item).asSingleResolution();
-    // The resolution is either unknown or resolved to the item or a visibility bridge.
-    assert resolution == null
-        || resolution.getResolvedMethod().method == item
-        || appView.unneededVisibilityBridgeMethods().contains(item);
+
+    assert resolution.isFailedResolution();
+
+    // If we can't resolve `item` it is questionable to record a renaming for it. However, it can
+    // be required to preserve errors.
+    //
+    // Example:
+    //   class A { private void m() }
+    //   class B extends A {}
+    //   class Main { public static void main() { new B().m(); } }
+    //
+    // In this example, the invoke-virtual instruction targeting m() in Main does not resolve,
+    // since the method is private. On the JVM this fails with an IllegalAccessError.
+    //
+    // If A.m() is renamed to A.a(), and the invoke-virtual instruction in Main is not changed to
+    // target a(), then the program will start failing with a NoSuchMethodError instead of an
+    // IllegalAccessError.
+    resolution
+        .asFailedResolution()
+        .forEachFailureDependency(
+            failureDependence -> {
+              assert lookupName(method) == lookupName(failureDependence.method);
+            });
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index d1c195f..b3694f7 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -135,7 +135,7 @@
    * <p>Normally, this means that the target corresponds to an actual definition that has been
    * renamed. For identity renamings, we are more relaxed, as no targets will be translated anyway.
    */
-  public abstract boolean checkTargetCanBeTranslated(DexMethod item);
+  public abstract boolean verifyRenamingConsistentWithResolution(DexMethod item);
 
   public final boolean verifyNoCollisions(
       Iterable<DexProgramClass> classes, DexItemFactory dexItemFactory) {
@@ -222,7 +222,7 @@
     }
 
     @Override
-    public boolean checkTargetCanBeTranslated(DexMethod item) {
+    public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
       return true;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
index 6c78e0b..e411211 100644
--- a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
@@ -173,7 +173,7 @@
   }
 
   @Override
-  public boolean checkTargetCanBeTranslated(DexMethod item) {
-    return namingLens.checkTargetCanBeTranslated(item);
+  public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
+    return namingLens.verifyRenamingConsistentWithResolution(item);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 942cb91..d37ef84 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -14,22 +14,13 @@
 import com.google.common.collect.Sets;
 import java.util.List;
 import java.util.Set;
-import java.util.function.Consumer;
 
 public class VisibilityBridgeRemover {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final Consumer<DexEncodedMethod> unneededVisibilityBridgeConsumer;
 
   public VisibilityBridgeRemover(AppView<AppInfoWithLiveness> appView) {
-    this(appView, null);
-  }
-
-  public VisibilityBridgeRemover(
-      AppView<AppInfoWithLiveness> appView,
-      Consumer<DexEncodedMethod> unneededVisibilityBridgeConsumer) {
     this.appView = appView;
-    this.unneededVisibilityBridgeConsumer = unneededVisibilityBridgeConsumer;
   }
 
   private void removeUnneededVisibilityBridgesFromClass(DexProgramClass clazz) {
@@ -51,9 +42,6 @@
           methodsToBeRemoved = Sets.newIdentityHashSet();
         }
         methodsToBeRemoved.add(method);
-        if (unneededVisibilityBridgeConsumer != null) {
-          unneededVisibilityBridgeConsumer.accept(method);
-        }
       }
     }
     if (methodsToBeRemoved != null) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 8c79dd7..4b599bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2755,7 +2755,7 @@
 
     // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
     for (DexProgramClass clazz : classesWithSerializableLambdas) {
-      clazz.removeDirectMethod(appView.dexItemFactory().deserializeLambdaMethod);
+      clazz.removeMethod(appView.dexItemFactory().deserializeLambdaMethod);
     }
 
     // Clear state before next fixed point iteration.
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 51c6934..6ab3fcd 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1103,6 +1103,7 @@
     public boolean alwaysUsePessimisticRegisterAllocation = false;
     public boolean enableCheckCastAndInstanceOfRemoval = true;
     public boolean enableDeadSwitchCaseElimination = true;
+    public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
@@ -1123,6 +1124,7 @@
     public boolean trackDesugaredAPIConversions =
         System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
     public boolean forceLibBackportsInL8CfToCf = false;
+    public boolean enumUnboxingRewriteJavaCGeneratedMethod = false;
 
     // TODO(b/144781417): This is disabled by default as some test apps appear to have such classes.
     public boolean allowNonAbstractClassesWithAbstractMethods = true;
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateAvailableApiExceptions.java b/src/test/java/com/android/tools/r8/androidapi/GenerateAvailableApiExceptions.java
new file mode 100644
index 0000000..83821f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateAvailableApiExceptions.java
@@ -0,0 +1,116 @@
+// 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.androidapi;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class GenerateAvailableApiExceptions {
+
+  private static final int fixedApiLevel = AndroidApiLevel.L.getLevel();
+
+  private static String generateBuildMapCode(Path apiVersionsXml) throws Exception {
+    CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(fixedApiLevel));
+    Map<DexClass, Boolean> isExceptionCache = new IdentityHashMap<>();
+    Int2ReferenceMap<Set<String>> exceptionsMap = new Int2ReferenceOpenHashMap<>();
+
+    // Read api-versions.xml locating all classes that derive throwable and adding them to the map.
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    Document document = factory.newDocumentBuilder().parse(apiVersionsXml.toFile());
+    NodeList classes = document.getElementsByTagName("class");
+    int totalCount = 0;
+    for (int i = 0; i < classes.getLength(); i++) {
+      Node node = classes.item(i);
+      assert node.getNodeType() == Node.ELEMENT_NODE;
+      Node sinceAttr = node.getAttributes().getNamedItem("since");
+      if (sinceAttr == null) {
+        continue;
+      }
+      int since = Integer.parseInt(sinceAttr.getNodeValue());
+      if (since >= fixedApiLevel) {
+        continue;
+      }
+      String name = node.getAttributes().getNamedItem("name").getNodeValue();
+      String type = DescriptorUtils.getJavaTypeFromBinaryName(name);
+      ClassSubject clazz = inspector.clazz(type);
+      if (isThrowable(clazz, isExceptionCache, inspector)) {
+        exceptionsMap.computeIfAbsent(since, k -> new TreeSet<>()).add(name);
+        totalCount++;
+      }
+    }
+
+    // Build the code for the buildMap method.
+    StringBuilder builder = new StringBuilder();
+    builder.append("public class DoNotCommit {");
+    builder.append("public static Set<DexType> build(DexItemFactory factory, int minApiLevel) {");
+    builder.append("  Set<DexType> types = SetUtils.newIdentityHashSet(" + totalCount + ");");
+    for (int api = 1; api < fixedApiLevel; api++) {
+      Set<String> names = exceptionsMap.get(api);
+      if (names == null || names.isEmpty()) {
+        continue;
+      }
+      builder.append("if (minApiLevel >= " + api + ") {");
+      for (String name : names) {
+        String desc = DescriptorUtils.getDescriptorFromClassBinaryName(name);
+        builder.append("  types.add(factory.createType(\"" + desc + "\"));");
+      }
+      builder.append("}");
+    }
+    builder.append("  return types;");
+    builder.append("}");
+    builder.append("}");
+
+    // Write to temp file and format that file.
+    String rawOutput = builder.toString();
+    Path tempFile = Files.createTempFile("output-", ".java");
+    FileUtils.writeTextFile(tempFile, rawOutput);
+    return MethodGenerationBase.formatRawOutput(tempFile);
+  }
+
+  private static boolean isThrowable(
+      ClassSubject clazz, Map<DexClass, Boolean> cache, CodeInspector inspector) {
+    if (!clazz.isPresent()) {
+      return false;
+    }
+    if (clazz.getDexClass().type == inspector.getFactory().objectType) {
+      return false;
+    }
+    if (clazz.getDexClass().type == inspector.getFactory().throwableType) {
+      return true;
+    }
+    return cache.computeIfAbsent(
+        clazz.getDexClass(),
+        c -> {
+          return c.superType != null
+              && isThrowable(inspector.clazz(c.superType.toSourceString()), cache, inspector);
+        });
+  }
+
+  public static void main(String[] args) throws Exception {
+    Path apiVersionsXml =
+        args.length == 1
+            ? Paths.get(args[0])
+            : Paths.get("<your-android-checkout>/development/sdk/api-versions.xml");
+    System.out.println(generateBuildMapCode(apiVersionsXml));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 6bc3814..32fa0b8 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -13,9 +13,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
@@ -73,7 +71,7 @@
         "ldc \"Hello World\"",
         "areturn");
 
-    // Generate a subclass with a method with same
+    // Generate a subclass with a bridge method targeting SuperClass.method().
     ClassBuilder subclass = jasminBuilder.addClass("SubClass", superClass.name);
     subclass.addBridgeMethod("getMethod", Collections.emptyList(), "Ljava/lang/String;",
         ".limit stack 1",
@@ -113,17 +111,8 @@
         .compile()
         .inspect(
             inspector -> {
-              ClassSubject classSubject = inspector.clazz(superClass.name);
-              assertThat(classSubject, isPresent());
-              MethodSubject methodSubject =
-                  classSubject.method("java.lang.String", "method", Collections.emptyList());
-              assertThat(methodSubject, isPresent());
-
-              classSubject = inspector.clazz(subclass.name);
-              assertThat(classSubject, isPresent());
-              methodSubject =
-                  classSubject.method("java.lang.String", "getMethod", Collections.emptyList());
-              assertThat(methodSubject, isPresent());
+              assertThat(inspector.clazz(superClass.name), not(isPresent()));
+              assertThat(inspector.clazz(subclass.name), not(isPresent()));
             })
         .run(parameters.getRuntime(), mainClass.name)
         .assertSuccessWithOutputLines("Hello World");
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
new file mode 100644
index 0000000..ebb7662
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -0,0 +1,106 @@
+// 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.bridgeremoval.hoisting;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.User;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BridgeHoistingAccessibilityTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public BridgeHoistingAccessibilityTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addInnerClasses(BridgeHoistingAccessibilityTestClasses.class)
+        .addProgramClassFileData(
+            transformer(B.class)
+                .setBridge(B.class.getDeclaredMethod("bridgeB", Object.class))
+                .transform(),
+            transformer(C.class)
+                .setBridge(C.class.getDeclaredMethod("bridgeC", Object.class))
+                .transform())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(BridgeHoistingAccessibilityTestClasses.A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+    assertThat(bClassSubject.uniqueMethodWithName("bridgeB"), isPresent());
+
+    ClassSubject cClassSubject = inspector.clazz(C.class);
+    assertThat(cClassSubject, isPresent());
+    // TODO(b/153147967): Should be hoisted to A.
+    assertThat(cClassSubject.uniqueMethodWithName("bridgeC"), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      C instance = new C();
+      System.out.print(instance.bridgeB("Hello"));
+      System.out.println(User.invokeBridgeC(instance));
+    }
+  }
+
+  @NeverMerge
+  static class B extends BridgeHoistingAccessibilityTestClasses.A {
+
+    // This bridge cannot be hoisted to A, since it would then become inaccessible to the call site
+    // in TestClass.main().
+    @NeverInline
+    /*bridge*/ String bridgeB(Object o) {
+      return (String) m((String) o);
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends B {
+
+    // This bridge is invoked from another package. However, this does not prevent us from hoisting
+    // the bridge to B, although B is not public, since users from outside this package can still
+    // access bridgeC() via class C. From B, the bridge can be hoisted again to A.
+    @NeverInline
+    public /*bridge*/ String bridgeC(Object o) {
+      return (String) m((String) o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/InterfaceBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/InterfaceBridgeHoistingTest.java
new file mode 100644
index 0000000..9fdc87f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/InterfaceBridgeHoistingTest.java
@@ -0,0 +1,78 @@
+// 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.bridgeremoval.hoisting;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceBridgeHoistingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceBridgeHoistingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class, I.class)
+        .addProgramClassFileData(
+            transformer(B.class)
+                .setBridge(B.class.getDeclaredMethod("bridge", Object.class))
+                .transform())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new B().bridge("Hello world!"));
+    }
+  }
+
+  @NeverMerge
+  static class A {}
+
+  @NeverMerge
+  interface I {
+
+    @NeverInline
+    default Object m(String arg) {
+      return System.currentTimeMillis() >= 0 ? arg : null;
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A implements I {
+
+    // Hoisting this bridge to class A would lead to a NoSuchMethodError since the targeted method
+    // is defined on the interface I.
+    @NeverInline
+    public /*bridge*/ String bridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/KeptBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/KeptBridgeHoistingTest.java
new file mode 100644
index 0000000..b4ce765
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/KeptBridgeHoistingTest.java
@@ -0,0 +1,90 @@
+// 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.bridgeremoval.hoisting;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeptBridgeHoistingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeptBridgeHoistingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(
+            transformer(B.class)
+                .setBridge(B.class.getDeclaredMethod("bridge", Object.class))
+                .transform())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keep class " + B.class.getTypeName() + " { java.lang.String bridge(...); }")
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+    assertThat(bClassSubject.uniqueMethodWithName("bridge"), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new B().bridge("Hello world!"));
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    @NeverInline
+    public Object m(String arg) {
+      return System.currentTimeMillis() >= 0 ? arg : null;
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+
+    // This bridge is kept by a Proguard configuration rule and must therefore not be hoisted to A.
+    @NeverInline
+    public /*bridge*/ String bridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
new file mode 100644
index 0000000..362f6a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
@@ -0,0 +1,89 @@
+// 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.bridgeremoval.hoisting;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.NonReboundBridgeHoistingTestClasses;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NonReboundBridgeHoistingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonReboundBridgeHoistingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addInnerClasses(NonReboundBridgeHoistingTestClasses.class)
+        .addProgramClassFileData(
+            transformer(C.class).setBridge(C.class.getDeclaredMethod("bridge")).transform())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(NonReboundBridgeHoistingTestClasses.getClassA());
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("bridge"), not(isPresent()));
+
+    ClassSubject bClassSubject = inspector.clazz(NonReboundBridgeHoistingTestClasses.B.class);
+    assertThat(bClassSubject, isPresent());
+
+    ClassSubject cClassSubject = inspector.clazz(C.class);
+    assertThat(cClassSubject, isPresent());
+    // TODO(b/153147967): This bridge should be hoisted to B.
+    assertThat(cClassSubject.uniqueMethodWithName("bridge"), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new C().bridge();
+    }
+  }
+
+  @NeverClassInline
+  static class C extends NonReboundBridgeHoistingTestClasses.B {
+
+    // The invoke instruction in this bridge cannot be rewritten to target A.m(), since A is not
+    // accessible in this context. It therefore points to B.m(), where there is no definition of the
+    // method. As a result of this, we cannot move this bridge to A without also rewriting the
+    // signature referenced from the invoke instruction.
+    @NeverInline
+    public /*bridge*/ void bridge() {
+      m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
new file mode 100644
index 0000000..353a9b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
@@ -0,0 +1,91 @@
+// 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.bridgeremoval.hoisting;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NonSuperclassBridgeHoistingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonSuperclassBridgeHoistingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(
+            transformer(B.class)
+                .setBridge(B.class.getDeclaredMethod("bridge", Object.class))
+                .transform())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertEquals(0, aClassSubject.getDexClass().virtualMethods().size());
+
+    ClassSubject b1ClassSubject = inspector.clazz(B.class);
+    assertThat(b1ClassSubject, isPresent());
+    assertThat(b1ClassSubject.uniqueMethodWithName("m"), isPresent());
+    assertThat(b1ClassSubject.uniqueMethodWithName("bridge"), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new B().bridge("Hello world!"));
+    }
+  }
+
+  @NeverMerge
+  static class A {}
+
+  @NeverClassInline
+  static class B extends A {
+
+    @NeverInline
+    public Object m(String arg) {
+      return System.currentTimeMillis() >= 0 ? arg : null;
+    }
+
+    // This bridge cannot be hoisted to A, since it targets a method on the enclosing class.
+    // Hoisting the bridge to A would lead to a NoSuchMethodError.
+    @NeverInline
+    public /*bridge*/ String bridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
new file mode 100644
index 0000000..50fc23c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -0,0 +1,146 @@
+// 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.bridgeremoval.hoisting;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PositiveBridgeHoistingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PositiveBridgeHoistingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class, B3.class)
+        .addProgramClassFileData(
+            transformer(B1.class)
+                .setBridge(B1.class.getDeclaredMethod("superBridge", Object.class))
+                .setBridge(B1.class.getDeclaredMethod("virtualBridge", Object.class))
+                .transform(),
+            transformer(B2.class)
+                .setBridge(B2.class.getDeclaredMethod("superBridge", Object.class))
+                .setBridge(B2.class.getDeclaredMethod("virtualBridge", Object.class))
+                .transform())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
+    // TODO(b/153147967): This should become present after hoisting.
+    assertThat(aClassSubject.uniqueMethodWithName("superBridge"), not(isPresent()));
+    // TODO(b/153147967): This should become present after hoisting.
+    assertThat(aClassSubject.uniqueMethodWithName("virtualBridge"), not(isPresent()));
+
+    ClassSubject b1ClassSubject = inspector.clazz(B1.class);
+    assertThat(b1ClassSubject, isPresent());
+    // TODO(b/153147967): This bridge should be hoisted to A.
+    assertThat(b1ClassSubject.uniqueMethodWithName("superBridge"), isPresent());
+    // TODO(b/153147967): This bridge should be hoisted to A.
+    assertThat(b1ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
+
+    ClassSubject b2ClassSubject = inspector.clazz(B2.class);
+    assertThat(b2ClassSubject, isPresent());
+    // TODO(b/153147967): This bridge should be hoisted to A.
+    assertThat(b2ClassSubject.uniqueMethodWithName("superBridge"), isPresent());
+    // TODO(b/153147967): This bridge should be hoisted to A.
+    assertThat(b2ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.print(new B1().superBridge("Hello"));
+      System.out.print(new B1().virtualBridge(" "));
+      System.out.print(new B2().superBridge("world"));
+      System.out.print(new B2().virtualBridge("!"));
+      System.out.println(new B3().m(""));
+    }
+  }
+
+  static class A {
+
+    @NeverInline
+    public Object m(String arg) {
+      return System.currentTimeMillis() >= 0 ? arg : null;
+    }
+  }
+
+  @NeverClassInline
+  static class B1 extends A {
+
+    // This bridge can be hoisted to A if the invoke-super instruction is rewritten to an
+    // invoke-virtual instruction.
+    @NeverInline
+    public /*bridge*/ String superBridge(Object o) {
+      return (String) super.m((String) o);
+    }
+
+    // This bridge can be hoisted to A.
+    @NeverInline
+    public /*bridge*/ String virtualBridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+
+  @NeverClassInline
+  static class B2 extends A {
+
+    // By hoisting B1.superBridge() to A this method bridge redundant.
+    @NeverInline
+    public /*bridge*/ String superBridge(Object o) {
+      return (String) super.m((String) o);
+    }
+
+    // By hoisting B1.virtualBridge() to A this method bridge redundant.
+    @NeverInline
+    public /*bridge*/ String virtualBridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+
+  // The fact that this class does not declare superBridge() or virtualBridge() should not prevent
+  // us from hoisting these bridges from B1/B2 to A.
+  //
+  // Strictly speaking, there could be an invoke-virtual instruction that targets B3.virtualBridge()
+  // and fails with a NoSuchMethodError in the input program. After hoisting B1.virtualBridge() to A
+  // such an instruction would no longer fail with a NoSuchMethodError in the generated program.
+  //
+  // If this ever turns out be an issue, it would be possible to track if there is an invoke
+  // instruction targeting B3.virtualBridge() that fails with a NoSuchMethodError in the Enqueuer,
+  // but this should never be the case in practice.
+  @NeverClassInline
+  static class B3 extends A {}
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
new file mode 100644
index 0000000..bc91e00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -0,0 +1,29 @@
+// 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.bridgeremoval.hoisting.testclasses;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest;
+
+public class BridgeHoistingAccessibilityTestClasses {
+
+  @NeverMerge
+  public static class A {
+
+    @NeverInline
+    public Object m(String arg) {
+      return System.currentTimeMillis() > 0 ? arg : null;
+    }
+  }
+
+  public static class User {
+
+    @NeverInline
+    public static String invokeBridgeC(BridgeHoistingAccessibilityTest.C instance) {
+      return instance.bridgeC(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/NonReboundBridgeHoistingTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/NonReboundBridgeHoistingTestClasses.java
new file mode 100644
index 0000000..f66c6a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/NonReboundBridgeHoistingTestClasses.java
@@ -0,0 +1,27 @@
+// 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.bridgeremoval.hoisting.testclasses;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+public class NonReboundBridgeHoistingTestClasses {
+
+  public static Class<A> getClassA() {
+    return A.class;
+  }
+
+  @NeverMerge
+  static class A {
+
+    @NeverInline
+    public void m() {
+      System.out.println("Hello world!");
+    }
+  }
+
+  @NeverMerge
+  public static class B extends A {}
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 3240d08..9cffd4e 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -47,7 +47,7 @@
 
   protected abstract List<Class<?>> getMethodTemplateClasses();
 
-  private String getHeaderString(Class<? extends MethodGenerationBase> generationClass) {
+  public static String getHeaderString(Class<?> generationClass, String generatedPackage) {
     int year = Calendar.getInstance().get(Calendar.YEAR);
     String simpleName = generationClass.getSimpleName();
     return StringUtils.lines(
@@ -59,7 +59,7 @@
         "// GENERATED FILE. DO NOT EDIT! See " + simpleName + ".java.",
         "// ***********************************************************************************",
         "",
-        "package " + getGeneratedClassPackageName() + ";");
+        "package " + generatedPackage + ";");
   }
 
   protected Path getGeneratedFile() {
@@ -116,7 +116,7 @@
 
   private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
     try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
-      printer.print(getHeaderString(this.getClass()));
+      printer.print(getHeaderString(this.getClass(), getGeneratedClassPackageName()));
       printer.println("import com.android.tools.r8.graph.DexItemFactory;");
       codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
       printer.println("public final class " + getGeneratedClassName() + " {\n");
@@ -131,7 +131,7 @@
     }
   }
 
-  private String formatRawOutput(Path tempFile) throws IOException {
+  public static String formatRawOutput(Path tempFile) throws IOException {
     // Apply google format.
     ProcessBuilder builder =
         new ProcessBuilder(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
index c97b7a7..d9c9350 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
@@ -22,6 +22,7 @@
     EnumVarArgs.class,
     EnumArrayReadWriteNoEscape.class,
     EnumArrayReadWrite.class,
+    EnumArrayNullRead.class,
     Enum2DimArrayReadWrite.class
   };
 
@@ -144,6 +145,24 @@
     }
   }
 
+  static class EnumArrayNullRead {
+
+    @SuppressWarnings("ConstantConditions")
+    public static void main(String[] args) {
+      try {
+        System.out.println(((MyEnum[]) null)[0]);
+      } catch (NullPointerException ignored) {
+      }
+    }
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B,
+      C;
+    }
+  }
+
   static class Enum2DimArrayReadWrite {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
new file mode 100644
index 0000000..6fb5fd1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -0,0 +1,126 @@
+// 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.enumunboxing;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumUnboxingClassStaticizerTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public EnumUnboxingClassStaticizerTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(EnumUnboxingClassStaticizerTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .enableNeverClassInliningAnnotations()
+            .enableInliningAnnotations()
+            .noMinification() // For assertions.
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::assertClassStaticized)
+            .inspectDiagnosticMessages(
+                m ->
+                    assertEnumIsUnboxed(
+                        UnboxableEnum.class,
+                        EnumUnboxingClassStaticizerTest.class.getSimpleName(),
+                        m))
+            .run(parameters.getRuntime(), TestClass.class)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  private void assertClassStaticized(CodeInspector codeInspector) {
+    if (parameters.isCfRuntime()) {
+      // There is no class staticizer in Cf.
+      assertThat(codeInspector.clazz(Companion.class).uniqueMethodWithName("method"), isPresent());
+      return;
+    }
+    MethodSubject method = codeInspector.clazz(CompanionHost.class).uniqueMethodWithName("method");
+    assertThat(method, isPresent());
+    assertEquals("int", method.getMethod().method.proto.parameters.toString());
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      CompanionHost.toKeep();
+      test(UnboxableEnum.A);
+      System.out.println("0");
+      test(UnboxableEnum.B);
+      System.out.println("1");
+      test(UnboxableEnum.C);
+      System.out.println("2");
+    }
+
+    @NeverInline
+    static void test(UnboxableEnum obj) {
+      // The class staticizer will move Companion.method() into CompanionHost.method().
+      // As a result of this transformation, this call site will need to be processed.
+      // To keep track of this, the staticizer will keep a reference to the method
+      // `void test(UnboxableEnum)`, but after enum unboxing this has been rewritten to
+      // `void test(int)`. Therefore, the staticizer's internal structure needs to be
+      // updated after enum unboxing.
+      CompanionHost.COMPANION.method(obj);
+    }
+  }
+
+  @NeverClassInline
+  static class CompanionHost {
+    static final Companion COMPANION = new Companion();
+
+    @NeverInline
+    static void toKeep() {
+      System.out.println("Keep me!");
+      System.out.println("Keep me!");
+    }
+  }
+
+  @NeverClassInline
+  static class Companion {
+    @NeverInline
+    public void method(UnboxableEnum obj) {
+      System.out.println(obj.ordinal());
+    }
+  }
+
+  @NeverClassInline
+  enum UnboxableEnum {
+    A,
+    B,
+    C;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index e367418..9f7b5bd 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -77,7 +77,9 @@
     Diagnostic diagnostic = m.getInfos().get(0);
     assertTrue(diagnostic.getDiagnosticMessage().startsWith("Unboxed enums"));
     assertTrue(
-        "Expected enum to be removed (" + testName + "): ",
+        StringUtils.joinLines(
+            "Expected enum to be removed (" + testName + "):",
+            m.getInfos().get(1).getDiagnosticMessage()),
         diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
   }
 
@@ -86,7 +88,7 @@
     Diagnostic diagnostic = m.getInfos().get(1);
     assertTrue(diagnostic.getDiagnosticMessage().startsWith("Boxed enums"));
     assertTrue(
-        "Expected enum NOT to be removed (" + testName + "): ",
+        "Expected enum NOT to be removed (" + testName + ")",
         diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java b/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java
new file mode 100644
index 0000000..728a346
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java
@@ -0,0 +1,72 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class JavaCGeneratedMethodTest extends EnumUnboxingTestBase {
+
+  private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public JavaCGeneratedMethodTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> classToTest = Ordinal.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(classToTest, ENUM_CLASS)
+            .addKeepMainRule(classToTest)
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .enableNeverClassInliningAnnotations()
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .addOptionsModification(
+                opt -> opt.testing.enumUnboxingRewriteJavaCGeneratedMethod = true)
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+            .run(parameters.getRuntime(), classToTest)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B,
+    C
+  }
+
+  static class Ordinal {
+
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.ordinal());
+      System.out.println(0);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 3b77ead..49718d4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -356,11 +357,8 @@
     assertEquals(1, instanceGetCount);
     assertEquals(0, invokeCount);
 
-    m =
-        clazz.method(
-            "int",
-            "moreControlFlows",
-            ImmutableList.of("inlining.A", "inlining.Nullability$Factor"));
+    // The enum parameter may get unboxed.
+    m = clazz.uniqueMethodWithName("moreControlFlows");
     assertTrue(m.isPresent());
 
     // Verify that a.b() is resolved to an inline instance-get.
@@ -372,6 +370,11 @@
       if (instruction.isInstanceGet()) {
         ++instanceGetCount;
       } else if (instruction.isInvoke()
+          && !instruction
+              .getMethod()
+              .name
+              .toString()
+              .equals(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_ORDINAL)
           && !((InvokeInstructionSubject) instruction)
               .holder()
               .toString()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
index d35a11d..bd87a92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.KeepUnusedArguments;
@@ -12,25 +13,57 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 /** Regression test for b/121119666. */
+@RunWith(Parameterized.class)
 public class ClassInliningOracleTest extends TestBase {
 
+  private final boolean enableInvokeSuperToInvokeVirtualRewriting;
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{1}, enable invoke-super to invoke-virtual rewriting: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public ClassInliningOracleTest(
+      boolean enableInvokeSuperToInvokeVirtualRewriting, TestParameters parameters) {
+    this.enableInvokeSuperToInvokeVirtualRewriting = enableInvokeSuperToInvokeVirtualRewriting;
+    this.parameters = parameters;
+  }
+
   @Test
   public void test() throws Exception {
-    CodeInspector inspector =
-        testForR8(Backend.DEX)
-            .addInnerClasses(ClassInliningOracleTest.class)
-            .addKeepMainRule(TestClass.class)
-            .enableInliningAnnotations()
-            .enableNeverClassInliningAnnotations()
-            .enableMergeAnnotations()
-            .enableUnusedArgumentAnnotations()
-            .compile()
-            .inspector();
-    assertThat(inspector.clazz(Builder.class), isPresent());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInliningOracleTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options ->
+                options.testing.enableInvokeSuperToInvokeVirtualRewriting =
+                    enableInvokeSuperToInvokeVirtualRewriting)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableUnusedArgumentAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              if (enableInvokeSuperToInvokeVirtualRewriting) {
+                assertThat(inspector.clazz(Builder.class), not(isPresent()));
+              } else {
+                assertThat(inspector.clazz(Builder.class), isPresent());
+              }
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
new file mode 100644
index 0000000..a85906b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
@@ -0,0 +1,124 @@
+// 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.devirtualize;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperToInvokeVirtualTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSuperToInvokeVirtualTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeSuperToInvokeVirtualTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+
+    MethodSubject negativeTestSubject = bClassSubject.uniqueMethodWithName("negativeTest");
+    assertThat(negativeTestSubject, isPresent());
+    assertTrue(negativeTestSubject.streamInstructions().anyMatch(this::isInvokeSuper));
+    assertTrue(
+        negativeTestSubject.streamInstructions().noneMatch(InstructionSubject::isInvokeVirtual));
+
+    MethodSubject positiveTestSubject = bClassSubject.uniqueMethodWithName("positiveTest");
+    assertThat(positiveTestSubject, isPresent());
+    assertTrue(positiveTestSubject.streamInstructions().noneMatch(this::isInvokeSuper));
+    assertTrue(
+        positiveTestSubject.streamInstructions().anyMatch(InstructionSubject::isInvokeVirtual));
+  }
+
+  private boolean isInvokeSuper(InstructionSubject instruction) {
+    if (parameters.isCfRuntime()) {
+      return instruction.isInvokeSpecial();
+    } else {
+      return instruction.asDexInstruction().isInvokeSuper();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new B().negativeTest();
+      new B().positiveTest();
+
+      if (System.currentTimeMillis() < 0) {
+        // To keep B.hello().
+        new B().hello();
+      }
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    @NeverInline
+    void hello() {
+      System.out.print("Hello");
+    }
+
+    @NeverInline
+    void world() {
+      System.out.println(" world!");
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+
+    @Override
+    void hello() {
+      System.out.println("Unexpected!");
+    }
+
+    @NeverInline
+    void negativeTest() {
+      super.hello();
+    }
+
+    @NeverInline
+    void positiveTest() {
+      super.world();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
new file mode 100644
index 0000000..15e37c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
@@ -0,0 +1,171 @@
+// 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.inliner;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Streams;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This test extends that of Regress131349148 for other API-introduced exceptions. */
+@RunWith(Parameterized.class)
+public class InlineCatchHandlerWithLibraryTypeTest extends TestBase {
+
+  private static final String TEMPLATE_CODE_EXCEPTION_BINARY_NAME = "java/lang/RuntimeException";
+
+  // A subset of exception types introduced in API levels between 16 to 24.
+  private static final Map<String, Integer> EXCEPTIONS =
+      ImmutableMap.<String, Integer>builder()
+          // VM 4.0.4 (api 15) is the first VM we have so no need to go prior to that.
+          .put("android.media.MediaCryptoException", 16)
+          .put("android.view.WindowManager$InvalidDisplayException", 17)
+          .put("android.media.DeniedByServerException", 18)
+          .put("android.media.ResourceBusyException", 19)
+          .put("java.lang.ReflectiveOperationException", 19)
+          .put("javax.crypto.AEADBadTagException", 19)
+          .put("android.system.ErrnoException", 21)
+          .put("android.media.MediaDrmResetException", 23)
+          .put("java.io.UncheckedIOException", 24)
+          .put("java.util.concurrent.CompletionException", 24)
+          // Verify error was fixed in 21 so up to 24 should catch post-fix issues.
+          .build();
+
+  private static final String EXPECTED = StringUtils.lines("Done...");
+
+  private final TestParameters parameters;
+  private final String exception;
+
+  @Parameters(name = "{0}, {1}")
+  public static List<Object[]> params() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        new TreeSet<>(EXCEPTIONS.keySet()));
+  }
+
+  public InlineCatchHandlerWithLibraryTypeTest(TestParameters parameters, String exception) {
+    this.parameters = parameters;
+    this.exception = exception;
+  }
+
+  private String getExceptionBinaryName() {
+    return DescriptorUtils.getBinaryNameFromJavaType(exception);
+  }
+
+  private byte[] getClassWithCatchHandler() throws IOException {
+    return transformer(ClassWithCatchHandler.class)
+        .transformTryCatchBlock(
+            "methodWithCatch",
+            (start, end, handler, type, continuation) -> {
+              String newType =
+                  type.equals(TEMPLATE_CODE_EXCEPTION_BINARY_NAME)
+                      ? getExceptionBinaryName()
+                      : type;
+              continuation.apply(start, end, handler, newType);
+            })
+        .transform();
+  }
+
+  private boolean compilationTargetIsMissingExceptionType() {
+    // A CF target could target any API in the end.
+    return parameters.isCfRuntime()
+        || parameters.getApiLevel().getLevel() < EXCEPTIONS.get(exception);
+  }
+
+  private boolean compileTargetHasVerificationBug() {
+    // A CF target could target any API in the end.
+    return parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableInliningAnnotations()
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(getClassWithCatchHandler())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        // Use the latest library so that all of the exceptions are defined.
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
+        .compile()
+        .inspect(this::checkInlined)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkResult);
+  }
+
+  private void checkResult(R8TestRunResult runResult) {
+    // The bootclasspath for our build of 4.4.4 does not contain various bits. Allow verify error.
+    if (!compilationTargetIsMissingExceptionType()
+        && parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V4_4_4)
+        && (exception.startsWith("android.media") || exception.startsWith("android.view"))) {
+      runResult.assertFailureWithErrorThatThrows(VerifyError.class);
+      return;
+    }
+    // Correct compilation should ensure that all programs run without error.
+    runResult.assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void checkInlined(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    boolean mainHasInlinedCatchHandler =
+        Streams.stream(classSubject.mainMethod().iterateTryCatches())
+            .anyMatch(tryCatch -> tryCatch.isCatching(exception));
+    if (compileTargetHasVerificationBug() && compilationTargetIsMissingExceptionType()) {
+      assertFalse(mainHasInlinedCatchHandler);
+    } else {
+      assertTrue(mainHasInlinedCatchHandler);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (args.length == 200) {
+        // Never called
+        ClassWithCatchHandler.methodWithCatch();
+      }
+      System.out.println("Done...");
+    }
+  }
+
+  static class ClassWithCatchHandler {
+
+    @NeverInline
+    public static void maybeThrow() {
+      if (System.nanoTime() > 0) {
+        throw new RuntimeException();
+      }
+    }
+
+    public static void methodWithCatch() {
+      try {
+        maybeThrow();
+      } catch (RuntimeException e) {
+        // We must use the exception, otherwise there is no move-exception that triggers the
+        // verification error.
+        System.out.println(e.getClass().getName());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithProgramTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithProgramTypeTest.java
new file mode 100644
index 0000000..bcb4b9b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithProgramTypeTest.java
@@ -0,0 +1,85 @@
+// 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.inliner;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Streams;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/** Simple test to ensure that we do inline program defined types used in catch handler guards. */
+@RunWith(Parameterized.class)
+public class InlineCatchHandlerWithProgramTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InlineCatchHandlerWithProgramTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InlineCatchHandlerWithProgramTypeTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Done...")
+        .inspect(this::checkInlined);
+  }
+
+  private void checkInlined(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    ClassSubject exceptionSubject = inspector.clazz(MyException.class);
+    boolean mainHasInlinedCatchHandler =
+        Streams.stream(classSubject.mainMethod().iterateTryCatches())
+            .anyMatch(tryCatch -> tryCatch.isCatching(exceptionSubject.getFinalName()));
+    assertTrue(mainHasInlinedCatchHandler);
+  }
+
+  static class MyException extends RuntimeException {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (args.length == 200) {
+        // Never called
+        ClassWithCatchHandler.methodWithCatch();
+      }
+      System.out.println("Done...");
+    }
+  }
+
+  static class ClassWithCatchHandler {
+
+    @NeverInline
+    public static void maybeThrow() {
+      if (System.nanoTime() > 0) {
+        throw new MyException();
+      }
+    }
+
+    public static void methodWithCatch() {
+      try {
+        maybeThrow();
+      } catch (MyException e) {
+        System.out.println(e.getClass().getName());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
index 98a6e9e..287fe21 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
@@ -49,7 +49,7 @@
             .anyMatch(
                 tryCatch -> tryCatch.isCatching("java.lang.ReflectiveOperationException"));
     int runtimeLevel = parameters.getApiLevel().getLevel();
-    assertEquals(runtimeLevel >= AndroidApiLevel.L.getLevel(), mainHasReflectiveOperationException);
+    assertEquals(runtimeLevel >= AndroidApiLevel.K.getLevel(), mainHasReflectiveOperationException);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java
index 7a5300d..176e6f5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValuesLengthTest.java
@@ -11,10 +11,15 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -57,6 +62,7 @@
   @Test
   public void testValuesLengthSwitchMapRemoved() throws Exception {
     // Make sure SwitchMap can still be removed with valuesLength optimization.
+    assertSwitchMapPresent();
     testForR8(parameters.getBackend())
         .addKeepMainRule(Main.class)
         .addInnerClasses(EnumValuesLengthTest.class)
@@ -72,10 +78,15 @@
         .assertSuccessWithOutputLines("0", "2", "5", "a", "D", "c", "D");
   }
 
+  private void assertSwitchMapPresent() throws IOException {
+    Collection<Path> classFilesForInnerClasses =
+        ToolHelper.getClassFilesForInnerClasses(
+            Collections.singletonList(EnumValuesLengthTest.class));
+    assertTrue(classFilesForInnerClasses.stream().anyMatch(p -> p.toString().endsWith("$1.class")));
+  }
+
   private void assertSwitchMapRemoved(CodeInspector inspector) {
-    assertTrue(
-        inspector.allClasses().stream()
-            .noneMatch(c -> !c.getDexClass().isEnum() && !c.getFinalName().endsWith("Main")));
+    assertTrue(inspector.allClasses().stream().noneMatch(c -> c.getOriginalName().endsWith("$1")));
   }
 
   private void assertValuesLengthRemoved(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 38a6346..c67c66c 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -28,6 +28,7 @@
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
 public class ClassFileTransformer {
@@ -301,6 +302,10 @@
             });
   }
 
+  public ClassFileTransformer setBridge(Method method) {
+    return setAccessFlags(method, MethodAccessFlags::setBridge);
+  }
+
   public ClassFileTransformer setPublic(Method method) {
     return setAccessFlags(
         method,
@@ -405,6 +410,21 @@
     void apply(int opcode, String type);
   }
 
+  @FunctionalInterface
+  public interface TryCatchBlockTransform {
+    void visitTryCatchBlock(
+        Label start,
+        Label end,
+        Label handler,
+        String type,
+        TryCatchBlockTransformContinuation continuation);
+  }
+
+  @FunctionalInterface
+  public interface TryCatchBlockTransformContinuation {
+    void apply(Label start, Label end, Label handler, String type);
+  }
+
   public ClassFileTransformer transformMethodInsnInMethod(
       String methodName, MethodInsnTransform transform) {
     return addMethodTransformer(
@@ -437,6 +457,21 @@
         });
   }
 
+  public ClassFileTransformer transformTryCatchBlock(
+      String methodName, TryCatchBlockTransform transform) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+            if (getContext().method.getMethodName().equals(methodName)) {
+              transform.visitTryCatchBlock(start, end, handler, type, super::visitTryCatchBlock);
+            } else {
+              super.visitTryCatchBlock(start, end, handler, type);
+            }
+          }
+        });
+  }
+
   /** Abstraction of the MethodVisitor.visitLdcInsn method with its continuation. */
   @FunctionalInterface
   public interface LdcInsnTransform {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index b2c8b6f..2387655 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -50,6 +50,11 @@
   }
 
   @Override
+  public DexInstructionSubject asDexInstruction() {
+    return null;
+  }
+
+  @Override
   public boolean isFieldAccess() {
     return instruction instanceof CfFieldInstruction;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index e487726..0335a5d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -116,6 +116,11 @@
   }
 
   @Override
+  public DexInstructionSubject asDexInstruction() {
+    return this;
+  }
+
+  @Override
   public boolean isFieldAccess() {
     return isInstanceGet() || isInstancePut() || isStaticGet() || isStaticPut();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 6eba32b..c5ca6c9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -16,6 +16,8 @@
     DISALLOW
   };
 
+  DexInstructionSubject asDexInstruction();
+
   boolean isFieldAccess();
 
   boolean isInstancePut();
diff --git a/tools/internal_test.py b/tools/internal_test.py
index d8e8c62..9309237 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -111,6 +111,8 @@
       '--find-min-xmx-archive']
 
 def compile_with_memory_max_command(record):
+  # TODO(b/152939233): Remove this special handling when fixed.
+  factor = 1.25 if record['app'] == 'chrome' else 1.15
   return [] if 'skip-find-xmx-max' in record else [
       'tools/run_on_app.py',
       '--compiler=r8',
@@ -119,7 +121,7 @@
       '--version=%s' % record['version'],
       '--no-debug',
       '--no-build',
-      '--max-memory=%s' % int(record['oom-threshold'] * 1.15)
+      '--max-memory=%s' % int(record['oom-threshold'] * factor)
   ]
 
 def compile_with_memory_min_command(record):