Okhttp: `isCleartextTrafficPermitted()` fails on OpenJDK 8 + Robolectric

Created on 4 May 2016  Â·  39Comments  Â·  Source: square/okhttp

How to reproduce:

  • Install OpenJDK 8
  • Use current 3.3.0-SNAPSHOT version
  • new OkHttpClient(): The code fails in the static initializer, when trying to figure out the platform.

The code that breaks was introduced here: https://github.com/square/okhttp/issues/2513

Either the detection of the runtime is broken, or the code is wrong there. I'll try to figure out an automatic test for this.

bug needs info

Most helpful comment

I was able to get the test green. Here's what I did:

  1. Change to using OkHttp-3.3.1. The project was using a SNAPSHOT version, but 3.3 has since been released.
  2. Upgrade to robolectric 3.1-rc1.
  3. Added config annotation with SDK specified to the test class:
@RunWith(RobolectricGradleTestRunner.class)
@Config(sdk = 23)
public class CreateOkHttpClientTest {

As for why it works, I don't know. It appears the default SDK is 21. The bit I don't understand is why it isn't throwing a ClassNotFoundException given this class is only in SDK 23. The test project itself is depending on API 23, which may have something to do with it.

All 39 comments

This is when running tests with Roboelectric, BTW. So maybe the code determines, the Platform is android, when it's actually not.

I tried to write up a simple testcase that reproduces this, but apparently it truly is the combination of the following things that make the code fail:

  • OpenJDK 8
  • Current Snapshot
  • Roboelectric (needs to be an android project for this)

Okay, so because the setup is kinda contrived: Here is a sample that reproduces this...

https://github.com/kungfoo/okhttp-on-jdk-with-robolectric

Does it throw or return the wrong result?

It crashes/throws in the static initializer which results in a
ClassNotFoundException.
On May 4, 2016 14:54, "Jesse Wilson" [email protected] wrote:

Does it throw or return the wrong result?

—
You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
https://github.com/square/okhttp/issues/2533#issuecomment-216853566

@kungfoo could you paste the stacktrace?

Will do later when I get my laptop back online...
On May 4, 2016 16:56, "Jesse Wilson" [email protected] wrote:

@kungfoo https://github.com/kungfoo could you paste the stacktrace?

—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/square/okhttp/issues/2533#issuecomment-216890889

If you want to debug the code under the described circumstances, you can simply use gradle to run the sample project I created for this.

Here's the stacktrace:

at okhttp3.internal.Platform$Android.isCleartextTrafficPermitted(Platform.java:324)
    at okhttp3.OkHttpClient.<clinit>(OkHttpClient.java:73)
    at ch.ergon.sample.CreateOkHttpClientTest.canCreateAClient(CreateOkHttpClientTest.java:17)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251)
    at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
    at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:112)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:56)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)

Wanna submit a fix?

Might be able to do that, when I'm back out of the woods... :)
On May 5, 2016 06:09, "Jesse Wilson" [email protected] wrote:

Wanna submit a fix?

—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/square/okhttp/issues/2533#issuecomment-217073027

Here's the message for the target Exception of the InvocationTargetException that gets thrown when calling the offending Method.invoke

Method getInstance in android.security.NetworkSecurityPolicy not mocked. See http://g.co/androidstudio/not-mocked for details.

Sounds like you're not using Robolectric since that message comes from the
mockable jar.

On Thu, May 26, 2016 at 12:56 AM Eliezer Graber [email protected]
wrote:

Here's the message for the target Exception of the
InvocationTargetException that gets thrown when calling the offending
Method.invoke
https://github.com/square/okhttp/commit/e3cd9b9f1c9787c36d5ea82190bf1e2365377e66#diff-5803d99d45dfa6a23e51dd60d44f2de9R314

Method getInstance in android.security.NetworkSecurityPolicy not mocked.
See http://g.co/androidstudio/not-mocked for details.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
https://github.com/square/okhttp/issues/2533#issuecomment-221777400

I'm definitely using Robolectric (verified on the call stack), which is why I was confused. Could it be a classloader issue where Class.forname is using the platform class?

Maybe, but that would be a robolectric bug in its ParallelUniverse then. If
Class.forName didn't work then things like LayoutInflater wouldn't work
either.

On Thu, May 26, 2016 at 1:09 AM Eliezer Graber [email protected]
wrote:

I'm definitely using Robolectric (verified on the call stack), which is
why I was confused. Could it be a classloader issue where Class.forname
is using the platform class?

—
You are receiving this because you commented.

Reply to this email directly or view it on GitHub
https://github.com/square/okhttp/issues/2533#issuecomment-221778776

Could it be happening because I'm on Robolectric 2.4 (which I believe only supports up to API 18), and NetworkSecurityPolicy wasn't added until API 23?

Possibly. Try the latest.

On Thu, May 26, 2016 at 1:21 AM Eliezer Graber [email protected]
wrote:

Could it be happening because I'm on Robolectric 2.4 (which I believe only
supports up to API 18), and NetworkSecurityPolicy wasn't added until API
23?

—
You are receiving this because you commented.
Reply to this email directly or view it on GitHub
https://github.com/square/okhttp/issues/2533#issuecomment-221780284

We're pretty stuck on 2.4, so I used the project referenced above and changed the robolectric properties to use API 23. I was greeted by this when running the test:

java.lang.UnsupportedOperationException: Robolectric does not support API level 23.

Seems like a clear deficiency on their side then. Not sure what action
there would be for us to take.

On Thu, May 26, 2016 at 1:34 AM Eliezer Graber [email protected]
wrote:

We're pretty stuck on 2.4, so I used the project referenced above
https://github.com/square/okhttp/issues/2533#issuecomment-216811119 and
change the robolectric properties to use API 23. I was greeted by this when
running the test:

java.lang.UnsupportedOperationException: Robolectric does not support API
level 23.

—
You are receiving this because you commented.
Reply to this email directly or view it on GitHub
https://github.com/square/okhttp/issues/2533#issuecomment-221781720

Would wrapping it in a version check help?

It's already behind one. You're compiling with API 23 (or newer) and thus
OkHttp attempts to use APIs from that version.

On Thu, May 26, 2016 at 2:11 AM Eliezer Graber [email protected]
wrote:

Would wrapping it in a version check help?

—
You are receiving this because you commented.
Reply to this email directly or view it on GitHub
https://github.com/square/okhttp/issues/2533#issuecomment-221786178

Would that affect a runtime check? Something like:

Class c = Class.forName("android.os.Build$VERSION");
Field f = c.getDeclaredField("SDK_INT");
int apiLevel = f.getInt(null);

When I try that running in Robolectric it returns 21.

I'm getting

java.lang.AssertionError
    at okhttp3.internal.AndroidPlatform.isCleartextTrafficPermitted(AndroidPlatform.java:143)
    at okhttp3.OkHttpClient.<clinit>(OkHttpClient.java:73)
    at okhttp3.OkHttpClient$Builder.<init>(OkHttpClient.java:381)

with okhttp 3.3.1 and both robolectric 3.0 and 3.1-rc1
all good with okhttp 3.2.+

I was able to get the test green. Here's what I did:

  1. Change to using OkHttp-3.3.1. The project was using a SNAPSHOT version, but 3.3 has since been released.
  2. Upgrade to robolectric 3.1-rc1.
  3. Added config annotation with SDK specified to the test class:
@RunWith(RobolectricGradleTestRunner.class)
@Config(sdk = 23)
public class CreateOkHttpClientTest {

As for why it works, I don't know. It appears the default SDK is 21. The bit I don't understand is why it isn't throwing a ClassNotFoundException given this class is only in SDK 23. The test project itself is depending on API 23, which may have something to do with it.

I have a vague idea of why this works now, and I opened up an issue on robolectric project. Let's see what they say, https://github.com/robolectric/robolectric/issues/2478

Robolectric 3.1-rc1 is pretty much unusable right now, as running tests take ~10x longer. Any other workarounds?

Ouch. This (hideous) workaround also appears functional with 3.0,

@RunWith(RobolectricGradleTestRunner.class)
@Config(shadows = CreateOkHttpClientTest.MyNetworkSecurityPolicy.class)
public class CreateOkHttpClientTest {

  @Test
  public void canCreateAClient() throws Exception {
    OkHttpClient client = new OkHttpClient();
    assertNotNull(client);
  }

  @Implements(NetworkSecurityPolicy.class)
  public static class MyNetworkSecurityPolicy {
    @Implementation public static NetworkSecurityPolicy getInstance() {
      try {
        Class<?> shadow = MyNetworkSecurityPolicy.class.forName("android.security.NetworkSecurityPolicy");
        return (NetworkSecurityPolicy) shadow.newInstance();
      } catch (Exception e) {
        throw new AssertionError();
      }
    }

    @Implementation public boolean isCleartextTrafficPermitted() {
      return true;
    }
  }
}

@dave-r12 the workaround works, thanks for sharing. One minor nit is that you should use Class.forName() instead ;)

A truly terrible hack for those who can't upgrade to Robolectric 3+ is to add the following class to your test module under the android.security package. Probably not a good idea if you're testing the actual NetworkSecurityPolicy.

public class NetworkSecurityPolicy {
  private static final NetworkSecurityPolicy INSTANCE = new NetworkSecurityPolicy();

  public static NetworkSecurityPolicy getInstance() {
    return INSTANCE;
  }

  public boolean isCleartextTrafficPermitted() {
    return true;
  }
}

Try with OkHttp 3.4.0-RC1 or later? I moved a few things around here.
https://github.com/square/okhttp/pull/2686

This still happens with OkHttp 3.4.1 and worse yet, @dave-r12 's no longer works

Oh, It works if you add a String argument to the isCleartetTrafficPermitted shadow method. Here's the updated (hideous) workaround:

@Implements(NetworkSecurityPolicy.class)
public class NetworkSecurityPolicyWorkaround {
    @Implementation
    public static NetworkSecurityPolicy getInstance() {
        //noinspection OverlyBroadCatchBlock
        try {
            Class<?> shadow = Class.forName("android.security.NetworkSecurityPolicy");
            return (NetworkSecurityPolicy) shadow.newInstance();
        } catch (Exception e) {
            throw new AssertionError();
        }
    }

    @Implementation
    public boolean isCleartextTrafficPermitted(String hostname) {
        return true;
    }
}

I'm not using Roboeletric, but i'm in pointing towards API 24, with okhttp version 3.4.1:
My problem is that I'm getting a NullPointerException (when running UnitTests) in okhttp3.internal.platform.AndroidPlatform when trying to run method:

@Override public boolean isCleartextTrafficPermitted(String hostname) {
    try {
      Class<?> networkPolicyClass = Class.forName("android.security.NetworkSecurityPolicy");
      Method getInstanceMethod = networkPolicyClass.getMethod("getInstance");
      Object networkSecurityPolicy = getInstanceMethod.invoke(null);
      Method isCleartextTrafficPermittedMethod = networkPolicyClass
          .getMethod("isCleartextTrafficPermitted", String.class);
      return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy, hostname);
    } catch (ClassNotFoundException | NoSuchMethodException e) {
      return super.isCleartextTrafficPermitted(hostname);
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
      throw new AssertionError();
    }
  }

which comes from RealConnection::connect.
Debugging it, it happens on the actual invoke isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy, hostname).
This problem didn't happen to me at version 3.2.0...
Can anyone help me?

@felipecsl solution (for those who can't upgrade to Robolectric 3+) works in this case too. But I really wouldn't like to use that :(

Happens with OkHttp 3.5.0 and robolectric 3.1.4

We're running into the same issue with OkHttp 3.5.0. Instead of using Robolectric directly we use unmock (which relies on Robolectric's android-all jar).

I'm so glad I found this ticket, was going crazy trying to understand why okhttp suddenly had an exception when running with robolectric

I am also still seeing this, but have resolved it with same fix as @eygraber.
note that I'm not on OpenJDK as OP was

  • OkHttp 3.6.0
  • Robolectric 3.3 only for @Config(sdk = _) < 23
  • Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode) on OSX

add to unit-tests source-set only, eg: project-dir/app/src/test/java/android/security by default

package android.security;
// https://github.com/square/okhttp/issues/2533#issuecomment-223093100
public class NetworkSecurityPolicy {
    private static final NetworkSecurityPolicy INSTANCE = new NetworkSecurityPolicy();
    @SuppressLint("NewApi") public static NetworkSecurityPolicy getInstance() { return INSTANCE; }
    public boolean isCleartextTrafficPermitted() { return true; }
}

lastly note that this may break with OkHttp 3.7.x

Is there anything to do in OkHttp for this?

Reading this, seems like it is fixed in Robolectric 3.3

Was this page helpful?
0 / 5 - 0 ratings