├── settings.gradle ├── lib └── asm-all-5.0.4-unshrinked.jar ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── jcoro-api └── src │ └── main │ └── java │ └── org │ └── jcoro │ ├── ICoroRunnable.java │ ├── Async.java │ ├── Await.java │ └── Coro.java ├── .gitignore ├── readme.md ├── jcoro-agent └── src │ └── main │ └── java │ └── org │ └── jcoro │ ├── TransformResult.java │ ├── AsyncLambdaInfo.java │ ├── MethodId.java │ ├── Helpers.java │ ├── MethodAnalyzeResult.java │ ├── LambdasSearchVisitor.java │ ├── Program.java │ ├── MethodAnalyzer.java │ └── MethodAdapter.java ├── jcoro-app └── src │ ├── test │ └── java │ │ └── org │ │ └── jcoro │ │ └── tests │ │ ├── TryCatchTest.java │ │ ├── RecursiveCoroTest.java │ │ ├── TryFinallyTest.java │ │ ├── PreciseMethodNameTest.java │ │ ├── SimpleTest.java │ │ ├── DeferFuncTest.java │ │ ├── UnpatchableTest.java │ │ ├── InnerCoroTest.java │ │ └── LambdaTest.java │ └── main │ └── java │ ├── org │ └── jcoro │ │ ├── nio │ │ ├── ServerSocketChannel.java │ │ ├── ByteChannel.java │ │ ├── FileChannel.java │ │ └── SocketChannel.java │ │ ├── AsyncServer.java │ │ └── SyncaServer.java │ └── bedefaced │ └── experiments │ └── jcoro │ └── ProxyServer.java ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include("jcoro-api") 2 | include("jcoro-agent") 3 | include("jcoro-app") -------------------------------------------------------------------------------- /lib/asm-all-5.0.4-unshrinked.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elw00d/jcoro/HEAD/lib/asm-all-5.0.4-unshrinked.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elw00d/jcoro/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /jcoro-api/src/main/java/org/jcoro/ICoroRunnable.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | /** 4 | * @author elwood 5 | */ 6 | public interface ICoroRunnable extends Runnable { 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | jcoro-api/build/ 4 | jcoro-agent/build/ 5 | jcoro-analyzer/build/ 6 | jcoro-app/build/ 7 | jcoro-agent/jcoro-agent.iml 8 | jcoro-analyzer/jcoro-analyzer.iml 9 | jcoro-api/jcoro-api.iml 10 | jcoro-app/jcoro-app.iml 11 | jcoro.iml 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 22 17:55:40 MSK 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-all.zip 7 | -------------------------------------------------------------------------------- /jcoro-api/src/main/java/org/jcoro/Async.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author elwood 10 | */ 11 | @Target({ElementType.METHOD, ElementType.TYPE_USE}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Async { 14 | Await[] value(); 15 | } 16 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | jcoro 2 | === 3 | 4 | Build and run tests: 5 | 6 | ```bash 7 | gradlew :jcoro-app:build --rerun-tasks 8 | ``` 9 | 10 | Build and run SyncaServer: 11 | 12 | ```bash 13 | gradlew :jcoro-app:build 14 | java -cp jcoro-api/build/libs/jcoro-api-1.0.jar:jcoro-app/build/classes/instrumented org.jcoro.SyncaServer 15 | ``` 16 | 17 | (in Windows change `:` symbol to `;` between classpaths). 18 | 19 | After that you can check the server is alive using `curl`: 20 | 21 | ```bash 22 | curl -i "http://localhost:8080" 23 | ``` -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/TransformResult.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | /** 4 | * @author elwood 5 | */ 6 | public class TransformResult { 7 | private final boolean wasModified; 8 | private final String className; 9 | private final byte[] data; 10 | 11 | public TransformResult(boolean wasModified, String className, byte[] data) { 12 | this.wasModified = wasModified; 13 | this.className = className; 14 | this.data = data; 15 | } 16 | 17 | public byte[] getData() { 18 | return data; 19 | } 20 | 21 | public String getClassName() { 22 | return className; 23 | } 24 | 25 | public boolean wasModified() { 26 | return wasModified; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/AsyncLambdaInfo.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Result of async lambdas searching. 7 | * 8 | * @author elwood 9 | */ 10 | public class AsyncLambdaInfo { 11 | private final String desc; 12 | private final List declaredRestorePoints; 13 | 14 | public AsyncLambdaInfo(String desc, List declaredRestorePoints) { 15 | this.desc = desc; 16 | this.declaredRestorePoints = declaredRestorePoints; 17 | } 18 | 19 | /** 20 | * Descriptor of method passed to `invokedynamic` instruction. 21 | * Usually it describes a method accepting some parameters (closures, captured by lambda) 22 | * and returning instance of functional interface (ICoroRunnable, for example). 23 | */ 24 | public String getDesc() { 25 | return desc; 26 | } 27 | 28 | /** 29 | * List of restore points parsed from type annotations on lambda. 30 | */ 31 | public List getDeclaredRestorePoints() { 32 | return declaredRestorePoints; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /jcoro-api/src/main/java/org/jcoro/Await.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Note if change this, it is necessary to sync changes in parsing (using Asm). 10 | * 11 | * @author elwood 12 | */ 13 | @Target({ElementType.METHOD, ElementType.TYPE_USE}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface Await { 16 | /** 17 | * Name of method. 18 | */ 19 | String value() default ""; 20 | 21 | /** 22 | * Signature of method. May be needed to resolve ambiguities. 23 | * Not required. 24 | */ 25 | String desc() default ""; 26 | 27 | /** 28 | * Full class name of method owner. May be needed to resolve ambiguities. 29 | * Not required. 30 | */ 31 | String owner() default ""; 32 | 33 | /** 34 | * If method cannot be instrumented, this attribute should be set to `false`. 35 | * Not required, default value is `true`. 36 | */ 37 | boolean patchable() default true; 38 | } 39 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/TryCatchTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import org.jcoro.Await; 4 | import org.jcoro.Coro; 5 | import org.jcoro.ICoroRunnable; 6 | import org.jcoro.Async; 7 | import org.junit.Test; 8 | 9 | /** 10 | * todo : testify this 11 | * 12 | * @author elwood 13 | */ 14 | public class TryCatchTest { 15 | public static void main(String[] args) { 16 | new TryCatchTest().testCatch(); 17 | } 18 | 19 | @Test 20 | public void testCatch() { 21 | final Coro coro = Coro.initSuspended(new ICoroRunnable() { 22 | @Override 23 | @Async(@Await("yield")) 24 | public void run() { 25 | Coro coro = Coro.get(); 26 | try { 27 | foo(); 28 | coro.yield(); // unreachable 29 | System.out.println("unreachable"); 30 | } catch (RuntimeException e) { 31 | System.out.println("Catched exc"); 32 | } 33 | } 34 | }); 35 | coro.resume(); 36 | } 37 | 38 | public void foo() { 39 | throw new RuntimeException(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jcoro-app/src/main/java/org/jcoro/nio/ServerSocketChannel.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.nio; 2 | 3 | import org.jcoro.Await; 4 | import org.jcoro.Coro; 5 | import org.jcoro.Async; 6 | 7 | import java.nio.channels.AsynchronousServerSocketChannel; 8 | import java.nio.channels.AsynchronousSocketChannel; 9 | import java.nio.channels.CompletionHandler; 10 | 11 | /** 12 | * @author bedefaced 13 | */ 14 | public class ServerSocketChannel { 15 | @Async(@Await("yield")) 16 | public static AsynchronousSocketChannel accept(AsynchronousServerSocketChannel channel) { 17 | Coro coro = Coro.get(); 18 | final AsynchronousSocketChannel[] res = new AsynchronousSocketChannel[1]; 19 | final Throwable[] exc = new Throwable[1]; 20 | coro.yield(() -> channel.accept(null, new CompletionHandler() { 21 | @Override 22 | public void completed(AsynchronousSocketChannel result, Object attachment) { 23 | res[0] = result; 24 | coro.resume(); 25 | } 26 | 27 | @Override 28 | public void failed(Throwable e, Object attachment) { 29 | exc[0] = e; 30 | coro.resume(); 31 | } 32 | })); 33 | if (exc[0] != null) throw new RuntimeException(exc[0]); 34 | return res[0]; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/RecursiveCoroTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import junit.framework.Assert; 4 | import org.jcoro.Async; 5 | import org.jcoro.Await; 6 | import org.jcoro.Coro; 7 | import org.jcoro.ICoroRunnable; 8 | import org.junit.Test; 9 | 10 | /** 11 | * @author elwood 12 | */ 13 | public class RecursiveCoroTest { 14 | public static void main(String[] args) { 15 | new RecursiveCoroTest().test(); 16 | } 17 | 18 | @Test 19 | public void test() { 20 | final int[] state = new int[1]; 21 | final Coro coro = Coro.initSuspended(new ICoroRunnable() { 22 | private int i = 0; 23 | 24 | @Async({@Await("yield"), @Await("run")}) 25 | public void run() { 26 | final int _i = i; 27 | if (i != 1) { 28 | i = 1; 29 | run(); 30 | } 31 | final Coro _coro = Coro.get(); 32 | _coro.yield(); 33 | System.out.println(String.format("run(%d) ends", _i)); 34 | state[0] = _i; 35 | } 36 | }); 37 | coro.start(); 38 | coro.resume(); 39 | Assert.assertEquals(state[0], 1); 40 | coro.resume(); 41 | Assert.assertEquals(state[0], 0); 42 | System.out.println("Finished"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/MethodId.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | /** 4 | * @author elwood 5 | */ 6 | public class MethodId { 7 | public final String className; 8 | public final String methodName; 9 | public final String signature; 10 | 11 | public MethodId(String className, String methodName, String signature) { 12 | if (className == null) throw new IllegalArgumentException("className shouldn't be null"); 13 | if (methodName == null) throw new IllegalArgumentException("methodName shouldn't be null"); 14 | if (signature == null) throw new IllegalArgumentException("signature shouldn't be null"); 15 | this.className = className; 16 | this.methodName = methodName; 17 | this.signature = signature; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | 25 | MethodId methodId = (MethodId) o; 26 | 27 | if (!className.equals(methodId.className)) return false; 28 | if (!methodName.equals(methodId.methodName)) return false; 29 | return signature.equals(methodId.signature); 30 | 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | int result = className.hashCode(); 36 | result = 31 * result + methodName.hashCode(); 37 | result = 31 * result + signature.hashCode(); 38 | return result; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return String.format("%s.%s [%s]", className, methodName, signature); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/TryFinallyTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import org.jcoro.Async; 4 | import org.jcoro.Await; 5 | import org.jcoro.Coro; 6 | import org.jcoro.ICoroRunnable; 7 | import org.junit.Test; 8 | 9 | /** 10 | * todo : testify this 11 | * 12 | * @author elwood 13 | */ 14 | public class TryFinallyTest { 15 | public static void main(String[] args) { 16 | new TryFinallyTest().test2(); 17 | } 18 | 19 | @Test 20 | public void test() { 21 | final Coro coro = Coro.initSuspended(new ICoroRunnable() { 22 | @Override 23 | @Async(@Await("yield")) 24 | public void run() { 25 | try { 26 | System.out.println("Before yield"); 27 | Coro coro = Coro.get(); 28 | coro.yield(); 29 | System.out.println("After yield"); 30 | } finally { 31 | System.out.println("Finally"); 32 | } 33 | } 34 | }); 35 | coro.resume(); 36 | coro.resume(); 37 | } 38 | 39 | @Test 40 | public void test2() { 41 | final Coro coro = Coro.initSuspended(new ICoroRunnable() { 42 | @Override 43 | @Async(@Await("yield")) 44 | public void run() { 45 | try { 46 | try { 47 | System.out.println("Try"); 48 | } finally { 49 | System.out.println("Before yield"); 50 | Coro coro = Coro.get(); 51 | coro.yield(); 52 | System.out.println("After yield"); 53 | } 54 | } finally { 55 | System.out.println("Outer finally"); 56 | } 57 | } 58 | }); 59 | coro.resume(); 60 | coro.resume(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jcoro-app/src/main/java/org/jcoro/nio/ByteChannel.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.nio; 2 | 3 | import org.jcoro.Await; 4 | import org.jcoro.Coro; 5 | import org.jcoro.Async; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.nio.channels.AsynchronousByteChannel; 9 | import java.nio.channels.CompletionHandler; 10 | 11 | /** 12 | * @author elwood 13 | */ 14 | public class ByteChannel { 15 | @Async(@Await("yield")) 16 | public static Integer read(AsynchronousByteChannel channel, ByteBuffer dst) { 17 | final Coro coro = Coro.get(); 18 | final Integer[] res = new Integer[1]; 19 | final Throwable[] exc = new Throwable[1]; 20 | coro.yield(() -> channel.read(dst, null, new CompletionHandler() { 21 | @Override 22 | public void completed(Integer result, Object attachment) { 23 | res[0] = result; 24 | coro.resume(); 25 | } 26 | 27 | @Override 28 | public void failed(Throwable e, Object attachment) { 29 | exc[0] = e; 30 | coro.resume(); 31 | } 32 | })); 33 | if (exc[0] != null) throw new RuntimeException(exc[0]); 34 | return res[0]; 35 | } 36 | 37 | @Async(@Await("yield")) 38 | public static Integer write(AsynchronousByteChannel channel, ByteBuffer src) { 39 | final Coro coro = Coro.get(); 40 | final Integer[] res = new Integer[1]; 41 | final Throwable[] exc = new Throwable[1]; 42 | coro.yield(() -> channel.write(src, null, new CompletionHandler() { 43 | @Override 44 | public void completed(Integer result, Object attachment) { 45 | res[0] = result; 46 | coro.resume(); 47 | } 48 | 49 | @Override 50 | public void failed(Throwable e, Object attachment) { 51 | exc[0] = e; 52 | coro.resume(); 53 | } 54 | })); 55 | if (exc[0] != null) throw new RuntimeException(exc[0]); 56 | return res[0]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/PreciseMethodNameTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import org.jcoro.Async; 4 | import org.jcoro.Await; 5 | import org.jcoro.Coro; 6 | import org.jcoro.ICoroRunnable; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | import java.lang.reflect.Field; 11 | import java.util.Stack; 12 | 13 | /** 14 | * @author elwood 15 | */ 16 | public class PreciseMethodNameTest { 17 | public static void main(String[] args) { 18 | new PreciseMethodNameTest().test(); 19 | } 20 | 21 | @Test 22 | public void test() { 23 | Coro coro = Coro.initSuspended(new ICoroRunnable() { 24 | @Async({@Await(value = "bar", desc = "", owner = "org/jcoro/tests/PreciseMethodNameTest$Moo")}) 25 | public void run() { 26 | Foo foo = new Foo(); 27 | foo.bar(); 28 | Moo moo = new Moo(); 29 | moo.bar(); 30 | } 31 | }); 32 | coro.start(); 33 | 34 | // If foo.bar() will be instrumented, state after yielded moo.bar() will be 1 35 | // So we should check that state is equal to 0 36 | final Field statesStackField; 37 | final Stack statesStack; 38 | try { 39 | statesStackField = Coro.class.getDeclaredField("statesStack"); 40 | statesStackField.setAccessible(true); 41 | statesStack = (Stack) statesStackField.get(coro); 42 | } catch (NoSuchFieldException | IllegalAccessException e) { 43 | throw new RuntimeException(e); 44 | } 45 | Assert.assertTrue((Integer) statesStack.peek() == 0); 46 | coro.resume(); 47 | } 48 | 49 | private static class Foo { 50 | public void bar() { 51 | System.out.println("Foo::bar()"); 52 | } 53 | } 54 | 55 | private static class Moo { 56 | @Async({@Await("yield")}) 57 | public void bar() { 58 | System.out.println("Moo::bar() begin"); 59 | Coro.get().yield(); 60 | System.out.println("Moo::bar() end"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/SimpleTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import org.jcoro.Async; 4 | import org.jcoro.Await; 5 | import org.jcoro.Coro; 6 | import org.jcoro.ICoroRunnable; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | /** 11 | * @author elwood 12 | */ 13 | public class SimpleTest { 14 | public static void main(String[] args) { 15 | new SimpleTest().test(); 16 | } 17 | 18 | @Test 19 | @Async({@Await("assertTrue")}) 20 | public void test() { 21 | Assert.assertTrue(true); 22 | } 23 | 24 | @Test 25 | public void test2() { 26 | int[] array = new int[2]; 27 | array[0] = 0; 28 | array[1] = 0; 29 | Coro coro = Coro.initSuspended(new ICoroRunnable() { 30 | @Override 31 | @Async({@Await(value = "foo")}) 32 | public void run() { 33 | int i = 5; 34 | double f = 10; 35 | System.out.println("coro: func begin, i = " + i); 36 | // 37 | final String argStr = foo(i, f, "argStr"); 38 | // 39 | System.out.println("coro: func end, i: " + i + ", str: " + argStr); 40 | } 41 | 42 | @Async(@Await("yield")) 43 | private String foo(int x, double y, String m) { 44 | Assert.assertTrue(x == 5); 45 | Assert.assertTrue(y == 10); 46 | Assert.assertTrue(m.equals("argStr")); 47 | // 48 | Coro c = Coro.get(); 49 | c.yield(); 50 | array[0] = 1; 51 | // 52 | Assert.assertTrue(x == 5); 53 | Assert.assertTrue(y == 10); 54 | Assert.assertTrue(m.equals("argStr")); 55 | return "returnedStr"; 56 | } 57 | }); 58 | System.out.println("Starting coro"); 59 | coro.start(); 60 | System.out.println("Coro yielded"); 61 | Assert.assertTrue(array[0] == 0); 62 | coro.resume(); 63 | System.out.println("Coro finished"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/DeferFuncTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import org.jcoro.Async; 4 | import org.jcoro.Await; 5 | import org.jcoro.Coro; 6 | import org.jcoro.ICoroRunnable; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | /** 11 | * @author elwood 12 | */ 13 | public class DeferFuncTest { 14 | public static void main(String[] args) { 15 | new DeferFuncTest().testDeferFunc(); 16 | } 17 | 18 | @Test 19 | public void testDeferFunc() { 20 | int[] array = new int[2]; 21 | array[0] = 0; 22 | array[1] = 0; 23 | Coro coro = Coro.initSuspended(new ICoroRunnable() { 24 | @Override 25 | @Async({@Await(value = "foo")}) 26 | public void run() { 27 | int i = 5; 28 | double f = 10; 29 | System.out.println("coro: func begin, i = " + i); 30 | // 31 | final String argStr = foo(i, f, "argStr"); 32 | // 33 | System.out.println("coro: func end, i: " + i + ", str: " + argStr); 34 | } 35 | 36 | @Async(@Await("yield")) 37 | private String foo(int x, double y, String m) { 38 | Assert.assertTrue(x == 5); 39 | Assert.assertTrue(y == 10); 40 | Assert.assertTrue(m.equals("argStr")); 41 | // 42 | Coro c = Coro.get(); 43 | c.yield(() -> { 44 | System.out.println("Deferred func !"); 45 | c.resume(); 46 | Assert.assertTrue(array[0] == 1); 47 | }); 48 | array[0] = 1; 49 | // 50 | Assert.assertTrue(x == 5); 51 | Assert.assertTrue(y == 10); 52 | Assert.assertTrue(m.equals("argStr")); 53 | return "returnedStr"; 54 | } 55 | }); 56 | System.out.println("Starting coro"); 57 | coro.start(); 58 | System.out.println("Coro yielded"); 59 | // Assert.assertTrue(array[0] == 0); 60 | // coro.resume(); 61 | System.out.println("Coro finished"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/UnpatchableTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import org.jcoro.Async; 4 | import org.jcoro.Coro; 5 | import org.jcoro.ICoroRunnable; 6 | import org.jcoro.Await; 7 | import org.junit.Test; 8 | 9 | /** 10 | * todo : testify this 11 | * 12 | * @author elwood 13 | */ 14 | public class UnpatchableTest { 15 | public static void main(String[] args) { 16 | new UnpatchableTest().test(); 17 | } 18 | 19 | @Test 20 | public void test() { 21 | Coro coro = Coro.initSuspended(new ICoroRunnable() { 22 | @Async(@Await(value = "unpatchableMethod", patchable = false)) 23 | public void run() { 24 | try { 25 | unpatchableMethod(10); 26 | } catch (IllegalArgumentException e) { 27 | System.out.println("Successfully catched exception: " + e.getMessage()); 28 | } 29 | } 30 | 31 | public void unpatchableMethod(int i) { 32 | System.out.println(String.format("unpatchableMethod(%d) start", i)); 33 | patchableMethod(i); 34 | System.out.println(String.format("unpatchableMethod(%d) end", i)); 35 | } 36 | 37 | @Async(@Await(value = "unpatchableMethod2", patchable = false)) 38 | public void patchableMethod(int i) { 39 | System.out.println("patchableMethod(" + i + ")"); 40 | unpatchableMethod2(5, "string"); 41 | System.out.println("patchableMethod(" + i + ") end"); 42 | throw new IllegalArgumentException("SomeException"); 43 | } 44 | 45 | public void unpatchableMethod2(int a, String b) { 46 | System.out.println(String.format("unpatchableMethod2(%d, %s)", a, b)); 47 | patchableMethod2(a, b); 48 | System.out.println(String.format("unpatchableMethod2(%d, %s) end", a, b)); 49 | } 50 | 51 | @Async(@Await("yield")) 52 | public void patchableMethod2(int a, String b) { 53 | System.out.println(String.format("patchableMethod(%d, %s): before yield", a, b)); 54 | Coro.get().yield(); 55 | System.out.println(String.format("patchableMethod(%d, %s): after yield", a, b)); 56 | } 57 | }); 58 | coro.start(); 59 | System.out.println("Paused. Resuming.."); 60 | coro.resume(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/InnerCoroTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import junit.framework.Assert; 4 | import org.jcoro.Async; 5 | import org.jcoro.Await; 6 | import org.jcoro.Coro; 7 | import org.jcoro.ICoroRunnable; 8 | import org.junit.Test; 9 | 10 | /** 11 | * Test for one coro creates another coro inside, and both yield and resume in correct order. 12 | * 13 | * @author elwood 14 | */ 15 | public class InnerCoroTest { 16 | public static void main(String[] args) { 17 | new InnerCoroTest().test(); 18 | } 19 | 20 | private void assertStepIs(int step, int[] state){ 21 | Assert.assertEquals(step, state[0]); 22 | state[0]++; 23 | } 24 | 25 | @Test 26 | public void test() { 27 | final int[] state = new int[1]; 28 | state[0] = 0; 29 | Coro outerCoro = Coro.initSuspended(new ICoroRunnable() { 30 | @Override 31 | @Async(@Await("yield")) 32 | public void run() { 33 | Coro _outerCoro = Coro.get(); 34 | 35 | Coro innerCoro = Coro.initSuspended(new ICoroRunnable() { 36 | @Override 37 | @Async(@Await("yield")) 38 | public void run() { 39 | assertStepIs(2, state); 40 | Coro _innerCoro = Coro.get(); 41 | int i = 5; 42 | System.out.println("i = " + i); 43 | 44 | _innerCoro.yield(); 45 | 46 | assertStepIs(6, state); 47 | i = 10; 48 | System.out.println("i = " + i); 49 | } 50 | }); 51 | assertStepIs(1, state); 52 | System.out.println("Outer coro started"); 53 | 54 | innerCoro.start(); 55 | 56 | assertStepIs(3, state); 57 | System.out.println("Yielding from outer coro.."); 58 | 59 | _outerCoro.yield(); 60 | 61 | assertStepIs(5, state); 62 | System.out.println("Resuming inner coro.."); 63 | 64 | innerCoro.resume(); 65 | 66 | assertStepIs(7, state); 67 | System.out.println("Returning from outer coro"); 68 | } 69 | }); 70 | assertStepIs(0, state); 71 | outerCoro.start(); 72 | 73 | assertStepIs(4, state); 74 | System.out.println("Resuming outer coro"); 75 | 76 | outerCoro.resume(); 77 | 78 | assertStepIs(8, state); 79 | System.out.println("All finished"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/Helpers.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import org.objectweb.asm.tree.AnnotationNode; 4 | 5 | import java.lang.annotation.Annotation; 6 | import java.util.List; 7 | 8 | import static java.util.stream.Collectors.toList; 9 | 10 | /** 11 | * @author elwood 12 | */ 13 | public class Helpers { 14 | public static List parseAwaitAnnotations(List restorePoints) { 15 | return restorePoints.stream().map(annotationNode -> { 16 | String value = ""; 17 | String desc = ""; 18 | boolean patchable = true; 19 | String owner = ""; 20 | for (int i = 0; i < annotationNode.values.size(); i+= 2) { 21 | final String name = (String) annotationNode.values.get(i); 22 | switch (name) { 23 | case "value": { 24 | value = (String) annotationNode.values.get(i + 1); 25 | break; 26 | } 27 | case "desc": { 28 | desc = (String) annotationNode.values.get(i + 1); 29 | break; 30 | } 31 | case "owner": { 32 | owner = (String) annotationNode.values.get(i + 1); 33 | break; 34 | } 35 | case "patchable": { 36 | patchable = (Boolean) annotationNode.values.get(i + 1); 37 | break; 38 | } 39 | default:{ 40 | throw new UnsupportedOperationException("Unknown @Await property: " + name); 41 | } 42 | } 43 | } 44 | final String _value = value; 45 | final String _desc = desc; 46 | final String _owner = owner; 47 | final boolean _patchable = patchable; 48 | return new Await() { 49 | @Override 50 | public String value() { 51 | return _value; 52 | } 53 | 54 | @Override 55 | public String desc() { 56 | return _desc; 57 | } 58 | 59 | @Override 60 | public String owner() { 61 | return _owner; 62 | } 63 | 64 | @Override 65 | public boolean patchable() { 66 | return _patchable; 67 | } 68 | 69 | @Override 70 | public Class annotationType() { 71 | return Await.class; 72 | } 73 | }; 74 | }).collect(toList()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/MethodAnalyzeResult.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import org.objectweb.asm.tree.AbstractInsnNode; 4 | import org.objectweb.asm.tree.analysis.Frame; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | * @author elwood 10 | */ 11 | public class MethodAnalyzeResult { 12 | private final int restorePointCallsCount; 13 | private final Set restorePoints; // Can be null, if no restore points were found 14 | private final Set unpatchableRestorePoints; // Can be null, if no unpatchable restore points were found 15 | private final Frame[] frames; 16 | private final AbstractInsnNode[] insns; 17 | private final boolean rootLambda; 18 | 19 | public MethodAnalyzeResult(int restorePointCallsCount, 20 | Set restorePoints, 21 | Set unpatchableRestorePoints, 22 | Frame[] frames, 23 | AbstractInsnNode[] insns, 24 | boolean rootLambda) { 25 | this.restorePointCallsCount = restorePointCallsCount; 26 | this.restorePoints = restorePoints; 27 | this.unpatchableRestorePoints = unpatchableRestorePoints; 28 | this.frames = frames; 29 | this.insns = insns; 30 | this.rootLambda = rootLambda; 31 | } 32 | 33 | /** 34 | * Количество мест вызовов обнаруженных в теле метода точек восстановления. 35 | * Необходимо при инструментировании кода, чтобы знать, на сколько точек ветвить switch. 36 | */ 37 | public int getRestorePointCallsCount() { 38 | return restorePointCallsCount; 39 | } 40 | 41 | /** 42 | * Сигнатуры методов, вызовы которых были интерпретированы как вызовы точек восстановления. 43 | * Их может быть меньше restorePointCallsCount (если вызовов одних и тех же методов несколько). 44 | */ 45 | public Set getRestorePoints() { 46 | return restorePoints; 47 | } 48 | 49 | /** 50 | * Сигнатуры методов-точек восстановления, которые ведут в unpatchable код 51 | * (код, который не будет инструментирован). 52 | */ 53 | public Set getUnpatchableRestorePoints() { 54 | return unpatchableRestorePoints; 55 | } 56 | 57 | /** 58 | * Массив состояний фрейма. Размер массива равен количеству инструкций в теле метода. 59 | * Таким образом, для каждой инструкции есть состояние фрейма. 60 | */ 61 | public Frame[] getFrames() { 62 | return frames; 63 | } 64 | 65 | /** 66 | * Массив инструкций. Размер массива равен размеру массива frames. 67 | * (Параллельные массивы). 68 | */ 69 | public AbstractInsnNode[] getInsns() { 70 | return insns; 71 | } 72 | 73 | /** 74 | * True if lambda is considered as root lambda. 75 | * Root lambda should put on coro stack one extra item (null) when saving state 76 | * This is need to keep the coro stack balanced when resuming 77 | * (because resume() assumes ICoroRunnable.run() is always an instance method, but lambda method is not) 78 | */ 79 | public boolean isRootLambda() { 80 | return rootLambda; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /jcoro-app/src/main/java/org/jcoro/AsyncServer.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.nio.ByteBuffer; 6 | import java.nio.channels.AsynchronousServerSocketChannel; 7 | import java.nio.channels.AsynchronousSocketChannel; 8 | import java.nio.channels.CompletionHandler; 9 | import java.nio.charset.Charset; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | 14 | /** 15 | * @author elwood 16 | */ 17 | public class AsyncServer { 18 | static AtomicInteger openChannels = new AtomicInteger(); 19 | 20 | public static void main(String[] args) throws IOException, InterruptedException { 21 | final AsynchronousServerSocketChannel listener = 22 | AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000)); 23 | 24 | ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 25 | 26 | listener.accept(null, new CompletionHandler() { 27 | @Override 28 | public void completed(AsynchronousSocketChannel channel, Void attachment) { 29 | listener.accept(null, this); 30 | // openChannels.incrementAndGet(); 31 | 32 | executorService.submit(new Runnable() { 33 | @Override 34 | public void run() { 35 | handle(channel); 36 | } 37 | }); 38 | //handle(channel); 39 | } 40 | 41 | @Override 42 | public void failed(Throwable exc, Void attachment) { 43 | // 44 | } 45 | }); 46 | 47 | Thread.sleep(60000); 48 | } 49 | 50 | private static void handle(AsynchronousSocketChannel channel) { 51 | ByteBuffer buffer = ByteBuffer.allocate(10 * 1024); 52 | 53 | channel.read(buffer, null, new CompletionHandler() { 54 | @Override 55 | public void completed(Integer result, Void attachment) { 56 | ByteBuffer outBuffer = ByteBuffer.wrap("200 OK".getBytes(Charset.forName("utf-8"))); 57 | channel.write(outBuffer, null, new CompletionHandler() { 58 | @Override 59 | public void completed(Integer result, Void attachment) { 60 | //System.out.println("Response sent"); 61 | try { 62 | channel.close(); 63 | // int nOpen = openChannels.decrementAndGet(); 64 | // System.out.println("Open channels: " + nOpen); 65 | } catch (IOException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | 70 | @Override 71 | public void failed(Throwable exc, Void attachment) { 72 | } 73 | }); 74 | } 75 | 76 | @Override 77 | public void failed(Throwable exc, Void attachment) { 78 | 79 | } 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /jcoro-app/src/main/java/org/jcoro/SyncaServer.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.nio.ByteBuffer; 6 | import java.nio.channels.AsynchronousServerSocketChannel; 7 | import java.nio.channels.AsynchronousSocketChannel; 8 | import java.nio.charset.Charset; 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.concurrent.Executors; 11 | 12 | import static org.jcoro.nio.ServerSocketChannel.accept; 13 | import static org.jcoro.nio.SocketChannel.read; 14 | import static org.jcoro.nio.SocketChannel.write; 15 | 16 | /** 17 | * @author elwood 18 | */ 19 | public class SyncaServer { 20 | public static void main(String[] args) throws IOException, InterruptedException { 21 | Coro coro = Coro.initSuspended(new ICoroRunnable() { 22 | @Override 23 | @Async({@Await("accept")}) 24 | public void run() { 25 | try { 26 | ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 27 | 28 | final AsynchronousServerSocketChannel listener = 29 | AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080)); 30 | 31 | while (true) { 32 | AsynchronousSocketChannel channel = accept(listener); 33 | executorService.submit(new Runnable() { 34 | @Override 35 | public void run() { 36 | try { 37 | Coro handleCoro = Coro.initSuspended(new ICoroRunnable() { 38 | @Override 39 | @Async({@Await("handle")}) 40 | public void run() { 41 | try { 42 | handle(channel); 43 | } catch (Throwable e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | }); 48 | handleCoro.start(); 49 | } catch (Throwable e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | }); 54 | } 55 | } catch (IOException e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | }); 60 | coro.start(); 61 | System.out.println("After start"); 62 | Thread.sleep(60000); 63 | } 64 | 65 | @Async({@Await("read"), @Await("write")}) 66 | public static void handle(AsynchronousSocketChannel channel) { 67 | ByteBuffer buffer = ByteBuffer.allocate(10 * 1024); 68 | 69 | read(channel, buffer); 70 | ByteBuffer outBuffer = ByteBuffer.wrap(("HTTP/1.1 200 OK\n" + 71 | "Server: jcoro SyncaServer\n" + 72 | "Content-Language: ru\n" + 73 | "Content-Type: text/html; charset=utf-8\n" + 74 | "Content-Length: 0\n" + 75 | "Connection: close").getBytes(Charset.forName("utf-8"))); 76 | write(channel, outBuffer); 77 | 78 | try { 79 | channel.close(); 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /jcoro-app/src/test/java/org/jcoro/tests/LambdaTest.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.tests; 2 | 3 | import org.junit.Assert; 4 | import org.jcoro.Async; 5 | import org.jcoro.Await; 6 | import org.jcoro.Coro; 7 | import org.jcoro.ICoroRunnable; 8 | import org.junit.Test; 9 | 10 | /** 11 | * @author elwood 12 | */ 13 | public class LambdaTest { 14 | public static void main(String[] args) { 15 | new LambdaTest().testRootLamda(); 16 | } 17 | 18 | /** 19 | * Test checks that type annotations are used only from corresponding invokedynamic instruction, 20 | * and they dont affect all previous invokedynamics. 21 | */ 22 | @Test 23 | public void testNotInstrument() { 24 | ICoroRunnable pleaseDontInstrumentIt = () -> { 25 | System.out.println("Just a little bit code"); 26 | }; 27 | Object nullObj = (@Async({@Await(value = "yield")}) ICoroRunnable) null; 28 | 29 | // If method is instrumented, direct call will fail with IllegalStateException, and test will fail too 30 | pleaseDontInstrumentIt.run(); 31 | } 32 | 33 | @Test 34 | public void testRootLamda() { 35 | // We should annotate root ICoroRunnable lambda using this and only syntax 36 | // If we will use another interface (IMyCoroRunnable, for example), lambda will not be instrument as _root_ method 37 | // There are big difference between root lambda and non-root lambda because lambda is compiled to static method, 38 | // but called as instance method. So, to be compatible with usual instance methods, root lambda should put 39 | // one extra reference object (null) on coro stack before pausing. If another (not ICoroRunnable) interface will be used, 40 | // extra object will not be placed, and resume() will fail. 41 | int[] state = new int[1]; 42 | state[0] = 0; 43 | Coro coro = Coro.initSuspended((@Async({@Await(value = "yield")}) ICoroRunnable) () -> { 44 | System.out.println("Started"); 45 | state[0] = 1; 46 | Coro.get().yield(); 47 | state[0] = 2; 48 | System.out.println("Continued"); 49 | }); 50 | coro.start(); 51 | Assert.assertEquals(1, state[0]); 52 | System.out.println("Paused"); 53 | coro.resume(); 54 | Assert.assertEquals(2, state[0]); 55 | System.out.println("Finished"); 56 | } 57 | 58 | private interface IMyCoroRunnable extends ICoroRunnable { 59 | } 60 | 61 | @Test(expected = AssertionError.class) 62 | public void testInvalidRootLambda() { 63 | // This syntax is incorrect for root lambda ! See `testRootLamda` test 64 | IMyCoroRunnable runnable = (@Async({@Await(value = "yield")}) IMyCoroRunnable) () -> { 65 | Coro.get().yield(); 66 | }; 67 | Coro coro = Coro.initSuspended(runnable); 68 | coro.start(); 69 | coro.resume(); // Will fail with AssertionError 70 | } 71 | 72 | @Test 73 | public void testNonRootLambda() { 74 | int[] state = new int[1]; 75 | state[0] = 0; 76 | Coro coro = Coro.initSuspended((@Async({@Await(value = "run", patchable = false)}) ICoroRunnable) () -> { 77 | System.out.println("Started"); 78 | 79 | Runnable someFunc = (@Async({@Await("yield")}) Runnable) () -> { 80 | state[0] = 1; 81 | System.out.println("SomeFunc begin"); 82 | Coro.get().yield(); 83 | state[0] = 2; 84 | System.out.println("SomeFunc end"); 85 | }; 86 | 87 | someFunc.run(); 88 | System.out.println("Continued"); 89 | }); 90 | coro.start(); 91 | Assert.assertEquals(1, state[0]); 92 | System.out.println("Paused"); 93 | coro.resume(); 94 | Assert.assertEquals(2, state[0]); 95 | System.out.println("Finished"); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jcoro-app/src/main/java/org/jcoro/nio/FileChannel.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.nio; 2 | 3 | import org.jcoro.Async; 4 | import org.jcoro.Await; 5 | import org.jcoro.Coro; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.nio.channels.AsynchronousFileChannel; 9 | import java.nio.channels.CompletionHandler; 10 | import java.nio.channels.FileLock; 11 | 12 | /** 13 | * @author elwood 14 | */ 15 | public class FileChannel { 16 | @Async(@Await("yield")) 17 | public static FileLock lock(AsynchronousFileChannel channel) { 18 | final Coro coro = Coro.get(); 19 | final FileLock[] res = new FileLock[1]; 20 | final Throwable[] exc = new Throwable[1]; 21 | coro.yield(() -> channel.lock(null, new CompletionHandler() { 22 | @Override 23 | public void completed(FileLock result, Object attachment) { 24 | res[0] = result; 25 | coro.resume(); 26 | } 27 | 28 | @Override 29 | public void failed(Throwable e, Object attachment) { 30 | exc[0] = e; 31 | coro.resume(); 32 | } 33 | })); 34 | if (null != exc[0]) throw new RuntimeException(exc[0]); 35 | return res[0]; 36 | } 37 | 38 | @Async(@Await("yield")) 39 | public static FileLock lock(AsynchronousFileChannel channel, 40 | long position, 41 | long size, 42 | boolean shared) { 43 | final Coro coro = Coro.get(); 44 | final FileLock[] res = new FileLock[1]; 45 | final Throwable[] exc = new Throwable[1]; 46 | coro.yield(() -> channel.lock(position, size, shared, null, new CompletionHandler() { 47 | @Override 48 | public void completed(FileLock result, Object attachment) { 49 | res[0] = result; 50 | coro.resume(); 51 | } 52 | 53 | @Override 54 | public void failed(Throwable e, Object attachment) { 55 | exc[0] = e; 56 | coro.resume(); 57 | } 58 | })); 59 | if (null != exc[0]) throw new RuntimeException(exc[0]); 60 | return res[0]; 61 | } 62 | 63 | @Async(@Await("yield")) 64 | public static Integer read(AsynchronousFileChannel channel, 65 | ByteBuffer dst, 66 | long position) { 67 | final Coro coro = Coro.get(); 68 | final Integer[] res = new Integer[1]; 69 | final Throwable[] exc = new Throwable[1]; 70 | coro.yield(() -> channel.read(dst, position, null, new CompletionHandler() { 71 | @Override 72 | public void completed(Integer result, Object attachment) { 73 | res[0] = result; 74 | coro.resume(); 75 | } 76 | 77 | @Override 78 | public void failed(Throwable e, Object attachment) { 79 | exc[0] = e; 80 | coro.resume(); 81 | } 82 | })); 83 | if (null != exc[0]) throw new RuntimeException(exc[0]); 84 | return res[0]; 85 | } 86 | 87 | @Async(@Await("yield")) 88 | public static Integer write(AsynchronousFileChannel channel, 89 | ByteBuffer src, 90 | long position) { 91 | final Coro coro = Coro.get(); 92 | final Integer[] res = new Integer[1]; 93 | final Throwable[] exc = new Throwable[1]; 94 | coro.yield(() -> channel.write(src, position, null, new CompletionHandler() { 95 | @Override 96 | public void completed(Integer result, Object attachment) { 97 | res[0] = result; 98 | coro.resume(); 99 | } 100 | 101 | @Override 102 | public void failed(Throwable e, Object attachment) { 103 | exc[0] = e; 104 | coro.resume(); 105 | } 106 | })); 107 | if (null != exc[0]) throw new RuntimeException(exc[0]); 108 | return res[0]; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /jcoro-app/src/main/java/bedefaced/experiments/jcoro/ProxyServer.java: -------------------------------------------------------------------------------- 1 | package bedefaced.experiments.jcoro; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.net.UnknownHostException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.channels.AsynchronousServerSocketChannel; 9 | import java.nio.channels.AsynchronousSocketChannel; 10 | 11 | import org.jcoro.Async; 12 | import org.jcoro.Await; 13 | import org.jcoro.Coro; 14 | import org.jcoro.ICoroRunnable; 15 | import org.jcoro.nio.ServerSocketChannel; 16 | import org.jcoro.nio.SocketChannel; 17 | 18 | public class ProxyServer implements Runnable { 19 | 20 | public InetAddress hostAddress; 21 | public int portlocal; 22 | public InetAddress remoteAddress; 23 | public int portremote; 24 | public AsynchronousServerSocketChannel serverChannel; 25 | 26 | public ProxyServer(InetAddress hostAddress, int portlocal, 27 | String remotehost, int portremote) throws UnknownHostException { 28 | this.hostAddress = hostAddress; 29 | this.portlocal = portlocal; 30 | 31 | this.portremote = portremote; 32 | this.remoteAddress = InetAddress.getByName(remotehost); 33 | } 34 | 35 | public static void main(String[] args) throws NumberFormatException, 36 | UnknownHostException { 37 | if (args.length != 3) { 38 | System.err.println("usage: "); 39 | return; 40 | } 41 | new ProxyServer(null, Integer.valueOf(args[0]), args[1], 42 | Integer.valueOf(args[2])).run(); 43 | } 44 | 45 | @Override 46 | public void run() { 47 | try { 48 | serverChannel = AsynchronousServerSocketChannel.open(); 49 | 50 | InetSocketAddress isa = new InetSocketAddress(this.hostAddress, 51 | this.portlocal); 52 | serverChannel.bind(isa); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | return; 56 | } 57 | 58 | ByteBuffer readClientBuffer = ByteBuffer.allocate(32); 59 | ByteBuffer readRemoteBuffer = ByteBuffer.allocate(32); 60 | 61 | Coro acceptClientCoro = Coro.initSuspended(new ICoroRunnable() { 62 | @Override 63 | @Async({@Await("accept"), @Await("connect")}) 64 | public void run() { 65 | while (true) { 66 | AsynchronousSocketChannel client = ServerSocketChannel 67 | .accept(serverChannel); 68 | 69 | AsynchronousSocketChannel remote = null; 70 | try { 71 | remote = AsynchronousSocketChannel.open(); 72 | } catch (IOException e) { 73 | e.printStackTrace(); 74 | } 75 | 76 | final AsynchronousSocketChannel finalRemote = remote; 77 | 78 | SocketChannel.connect(remote, new InetSocketAddress( 79 | remoteAddress, portremote)); 80 | 81 | Coro readClientCoro = Coro 82 | .initSuspended(new ICoroRunnable() { 83 | 84 | @Override 85 | @Async({@Await("read"), @Await("write")}) 86 | public void run() { 87 | while (true) { 88 | SocketChannel.read(client, 89 | readClientBuffer); 90 | readClientBuffer.flip(); 91 | SocketChannel.write(finalRemote, 92 | readClientBuffer); 93 | } 94 | } 95 | 96 | }); 97 | 98 | Coro readRemoteCoro = Coro 99 | .initSuspended(new ICoroRunnable() { 100 | 101 | @Override 102 | @Async({@Await("read"), @Await("write")}) 103 | public void run() { 104 | while (true) { 105 | SocketChannel.read(finalRemote, 106 | readRemoteBuffer); 107 | readRemoteBuffer.flip(); 108 | SocketChannel.write(client, 109 | readRemoteBuffer); 110 | } 111 | } 112 | 113 | }); 114 | 115 | readClientCoro.start(); 116 | readRemoteCoro.start(); 117 | 118 | } 119 | } 120 | }); 121 | 122 | acceptClientCoro.start(); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/LambdasSearchVisitor.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import org.objectweb.asm.*; 4 | import org.objectweb.asm.tree.AnnotationNode; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import static java.util.stream.Collectors.toList; 11 | 12 | /** 13 | * Searches type-annotated (with @Async annotation) lambdas. 14 | * Type annotations can be placed before invokedynamic insn and before previous instruction (bug in javac?). 15 | * So, we should track every neighbor instructions. 16 | * 17 | * Found async lambdas are put on resultMap. 18 | * 19 | * @author elwood 20 | */ 21 | public class LambdasSearchVisitor extends ClassVisitor { 22 | private final Map resultMap; 23 | 24 | public LambdasSearchVisitor(Map resultMap) { 25 | super(Opcodes.ASM5); 26 | this.resultMap = resultMap; 27 | } 28 | 29 | @Override 30 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 31 | return new MethodVisitor(Opcodes.ASM5) { 32 | private List lastRestorePoints = null; 33 | private String lastDesc = null; 34 | private Handle lastLambda = null; 35 | 36 | @Override 37 | public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { 38 | if (TypeReference.CAST == new TypeReference(typeRef).getSort() 39 | && "Lorg/jcoro/Async;".equals(desc)) { 40 | return new AnnotationNode(Opcodes.ASM5, desc) { 41 | @Override 42 | public void visitEnd() { 43 | assert "value".equals(this.values.get(0)); 44 | List restorePoints = (List) this.values.get(1); 45 | lastRestorePoints = Helpers.parseAwaitAnnotations(restorePoints); 46 | checkIfRestorePointWasDefined(); 47 | super.visitEnd(); 48 | } 49 | }; 50 | } 51 | return super.visitInsnAnnotation(typeRef, typePath, desc, visible); 52 | } 53 | 54 | private void checkIfRestorePointWasDefined() { 55 | if (lastRestorePoints != null && lastDesc != null && lastLambda != null) { 56 | resultMap.put(new MethodId(lastLambda.getOwner(), lastLambda.getName(), lastLambda.getDesc()), 57 | new AsyncLambdaInfo(lastDesc, lastRestorePoints)); 58 | clearLastInvokeDynamicArgs(); 59 | } 60 | } 61 | 62 | @Override 63 | public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { 64 | lastDesc = desc; 65 | lastLambda = (Handle) bsmArgs[1]; 66 | checkIfRestorePointWasDefined(); 67 | super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); 68 | } 69 | 70 | private void clearLastInvokeDynamicArgs() { 71 | lastDesc = null; 72 | lastLambda = null; 73 | lastRestorePoints = null; 74 | } 75 | 76 | @Override 77 | public void visitInsn(int opcode) { 78 | clearLastInvokeDynamicArgs(); 79 | super.visitInsn(opcode); 80 | } 81 | 82 | @Override 83 | public void visitIntInsn(int opcode, int operand) { 84 | clearLastInvokeDynamicArgs(); 85 | super.visitIntInsn(opcode, operand); 86 | } 87 | 88 | @Override 89 | public void visitVarInsn(int opcode, int var) { 90 | clearLastInvokeDynamicArgs(); 91 | super.visitVarInsn(opcode, var); 92 | } 93 | 94 | @Override 95 | public void visitTypeInsn(int opcode, String type) { 96 | clearLastInvokeDynamicArgs(); 97 | super.visitTypeInsn(opcode, type); 98 | } 99 | 100 | @Override 101 | public void visitFieldInsn(int opcode, String owner, String name, String desc) { 102 | clearLastInvokeDynamicArgs(); 103 | super.visitFieldInsn(opcode, owner, name, desc); 104 | } 105 | 106 | @Override 107 | public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 108 | clearLastInvokeDynamicArgs(); 109 | super.visitMethodInsn(opcode, owner, name, desc, itf); 110 | } 111 | 112 | @Override 113 | public void visitMethodInsn(int opcode, String owner, String name, String desc) { 114 | clearLastInvokeDynamicArgs(); 115 | super.visitMethodInsn(opcode, owner, name, desc); 116 | } 117 | 118 | @Override 119 | public void visitJumpInsn(int opcode, Label label) { 120 | clearLastInvokeDynamicArgs(); 121 | super.visitJumpInsn(opcode, label); 122 | } 123 | 124 | @Override 125 | public void visitLdcInsn(Object cst) { 126 | clearLastInvokeDynamicArgs(); 127 | super.visitLdcInsn(cst); 128 | } 129 | 130 | @Override 131 | public void visitIincInsn(int var, int increment) { 132 | clearLastInvokeDynamicArgs(); 133 | super.visitIincInsn(var, increment); 134 | } 135 | 136 | @Override 137 | public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { 138 | clearLastInvokeDynamicArgs(); 139 | super.visitTableSwitchInsn(min, max, dflt, labels); 140 | } 141 | 142 | @Override 143 | public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 144 | clearLastInvokeDynamicArgs(); 145 | super.visitLookupSwitchInsn(dflt, keys, labels); 146 | } 147 | 148 | @Override 149 | public void visitMultiANewArrayInsn(String desc, int dims) { 150 | clearLastInvokeDynamicArgs(); 151 | super.visitMultiANewArrayInsn(desc, dims); 152 | } 153 | }; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /jcoro-app/src/main/java/org/jcoro/nio/SocketChannel.java: -------------------------------------------------------------------------------- 1 | package org.jcoro.nio; 2 | 3 | import org.jcoro.Async; 4 | import org.jcoro.Await; 5 | import org.jcoro.Coro; 6 | 7 | import java.net.SocketAddress; 8 | import java.nio.ByteBuffer; 9 | import java.nio.channels.AsynchronousSocketChannel; 10 | import java.nio.channels.CompletionHandler; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | * @author bedefaced 15 | */ 16 | public class SocketChannel { 17 | @Async(@Await("yield")) 18 | public static void connect(AsynchronousSocketChannel channel, SocketAddress remote) { 19 | Coro coro = Coro.get(); 20 | final Throwable[] exc = new Throwable[1]; 21 | coro.yield(() -> channel.connect(remote, null, new CompletionHandler() { 22 | @Override 23 | public void completed(Void result, Object attachment) { 24 | coro.resume(); 25 | } 26 | 27 | @Override 28 | public void failed(Throwable e, Object attachment) { 29 | exc[0] = e; 30 | coro.resume(); 31 | } 32 | })); 33 | if (exc[0] != null) throw new RuntimeException(exc[0]); 34 | } 35 | 36 | @Async(@Await("yield")) 37 | public static Integer read(AsynchronousSocketChannel channel, ByteBuffer buffer) { 38 | Coro coro = Coro.get(); 39 | final Integer[] res = new Integer[1]; 40 | final Throwable[] exc = new Throwable[1]; 41 | coro.yield(() -> channel.read(buffer, null, new CompletionHandler() { 42 | @Override 43 | public void completed(Integer result, Object attachment) { 44 | res[0] = result; 45 | coro.resume(); 46 | } 47 | 48 | @Override 49 | public void failed(Throwable e, Object attachment) { 50 | exc[0] = e; 51 | coro.resume(); 52 | } 53 | })); 54 | if (exc[0] != null) throw new RuntimeException(exc[0]); 55 | return res[0]; 56 | } 57 | 58 | @Async(@Await("yield")) 59 | public static Integer read(AsynchronousSocketChannel channel, ByteBuffer buffer, long timeout, TimeUnit unit) { 60 | Coro coro = Coro.get(); 61 | final Integer[] res = new Integer[1]; 62 | final Throwable[] exc = new Throwable[1]; 63 | coro.yield(() -> channel.read(buffer, timeout, unit, null, new CompletionHandler() { 64 | @Override 65 | public void completed(Integer result, Object attachment) { 66 | res[0] = result; 67 | coro.resume(); 68 | } 69 | 70 | @Override 71 | public void failed(Throwable e, Object attachment) { 72 | exc[0] = e; 73 | coro.resume(); 74 | } 75 | })); 76 | if (exc[0] != null) throw new RuntimeException(exc[0]); 77 | return res[0]; 78 | } 79 | 80 | @Async(@Await("yield")) 81 | public static Long read(AsynchronousSocketChannel channel, ByteBuffer[] dsts, int offset, 82 | int length, long timeout, TimeUnit unit) { 83 | Coro coro = Coro.get(); 84 | final Long[] res = new Long[1]; 85 | final Throwable[] exc = new Throwable[1]; 86 | coro.yield(() -> channel.read(dsts, offset, length, timeout, unit, null, new CompletionHandler() { 87 | @Override 88 | public void completed(Long result, Object attachment) { 89 | res[0] = result; 90 | coro.resume(); 91 | } 92 | 93 | @Override 94 | public void failed(Throwable e, Object attachment) { 95 | exc[0] = e; 96 | coro.resume(); 97 | } 98 | })); 99 | if (exc[0] != null) throw new RuntimeException(exc[0]); 100 | return res[0]; 101 | } 102 | 103 | @Async(@Await("yield")) 104 | public static Integer write(AsynchronousSocketChannel channel, ByteBuffer buffer) { 105 | Coro coro = Coro.get(); 106 | final Integer[] res = new Integer[1]; 107 | final Throwable[] exc = new Throwable[1]; 108 | coro.yield(() -> channel.write(buffer, null, new CompletionHandler() { 109 | @Override 110 | public void completed(Integer result, Object attachment) { 111 | res[0] = result; 112 | coro.resume(); 113 | } 114 | 115 | @Override 116 | public void failed(Throwable e, Object attachment) { 117 | exc[0] = e; 118 | coro.resume(); 119 | } 120 | })); 121 | if (exc[0] != null) throw new RuntimeException(exc[0]); 122 | return res[0]; 123 | } 124 | 125 | @Async(@Await("yield")) 126 | public static Integer write(AsynchronousSocketChannel channel, ByteBuffer buffer, long timeout, TimeUnit unit) { 127 | Coro coro = Coro.get(); 128 | final Integer[] res = new Integer[1]; 129 | final Throwable[] exc = new Throwable[1]; 130 | coro.yield(() -> channel.write(buffer, timeout, unit, null, new CompletionHandler() { 131 | @Override 132 | public void completed(Integer result, Object attachment) { 133 | res[0] = result; 134 | coro.resume(); 135 | } 136 | 137 | @Override 138 | public void failed(Throwable e, Object attachment) { 139 | exc[0] = e; 140 | coro.resume(); 141 | } 142 | })); 143 | if (exc[0] != null) throw new RuntimeException(exc[0]); 144 | return res[0]; 145 | } 146 | 147 | @Async(@Await("yield")) 148 | public static Long write(AsynchronousSocketChannel channel, ByteBuffer[] dsts, int offset, 149 | int length, long timeout, TimeUnit unit) { 150 | Coro coro = Coro.get(); 151 | final Long[] res = new Long[1]; 152 | final Throwable[] exc = new Throwable[1]; 153 | coro.yield(() -> channel.write(dsts, offset, length, timeout, unit, null, new CompletionHandler() { 154 | @Override 155 | public void completed(Long result, Object attachment) { 156 | res[0] = result; 157 | coro.resume(); 158 | } 159 | 160 | @Override 161 | public void failed(Throwable e, Object attachment) { 162 | exc[0] = e; 163 | coro.resume(); 164 | } 165 | })); 166 | if (exc[0] != null) throw new RuntimeException(exc[0]); 167 | return res[0]; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/Program.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import org.objectweb.asm.*; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.net.MalformedURLException; 8 | import java.net.URL; 9 | import java.net.URLClassLoader; 10 | import java.nio.file.Files; 11 | import java.nio.file.StandardOpenOption; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * @author elwood 19 | */ 20 | public class Program { 21 | private static URLClassLoader classLoader; 22 | 23 | private static String sourceDirPath; 24 | private static String destDirPath; 25 | 26 | /** 27 | * Должно быть 4 аргумента: 28 | * --source src --dest dst 29 | */ 30 | public static void main(String[] args) { 31 | System.out.println("Instrumenting program started"); 32 | 33 | if (args.length != 4 || !args[0].equals("--source") || !args[2].equals("--dest")) { 34 | System.out.println("Usage: program --source --dest "); 35 | return; 36 | } 37 | 38 | sourceDirPath = args[1]; 39 | destDirPath = args[3]; 40 | 41 | prepareEnv(); 42 | new Program().instrumentClasses(); 43 | } 44 | 45 | /** 46 | * Validates sourceDir and destDir. 47 | * Checks if sourceDir exists; creates destDir if doesn't exist. 48 | */ 49 | private static void prepareEnv() { 50 | File sourceDir = new File(sourceDirPath); 51 | if (!sourceDir.exists()) { 52 | System.out.println("Source directory not found"); 53 | System.exit(-1); 54 | } 55 | File destDir = new File(destDirPath); 56 | if (!destDir.exists()) { 57 | if (!destDir.mkdirs()) { 58 | System.out.println("Cannot create destination directory"); 59 | System.exit(-1); 60 | } 61 | } 62 | } 63 | 64 | private void instrumentClasses() { 65 | File sourceDir = new File(sourceDirPath); 66 | List allClassFiles = new ArrayList<>(); 67 | collectClassFilesRecursively(allClassFiles, sourceDir); 68 | 69 | // Initialize classloader 70 | try { 71 | classLoader = new URLClassLoader( 72 | new URL[]{sourceDir.toURI().toURL()}, 73 | Thread.currentThread().getContextClassLoader() 74 | ); 75 | } catch (MalformedURLException e) { 76 | throw new RuntimeException(e); 77 | } 78 | 79 | for (File classFile : allClassFiles) { 80 | final byte[] bytes; 81 | try { 82 | bytes = Files.readAllBytes(classFile.toPath()); 83 | } catch (IOException e) { 84 | throw new RuntimeException(e); 85 | } 86 | final TransformResult transformResult = transform(bytes); 87 | 88 | final String[] parts = transformResult.getClassName().split("/"); 89 | final String onlyName = parts[parts.length - 1]; 90 | 91 | // Create all directories for package 92 | File dir = new File(destDirPath); 93 | for (int i = 0; i < parts.length - 1; i++) { 94 | String subdirName = parts[i]; 95 | File subdir = new File(dir, subdirName); 96 | if (!subdir.exists()) { 97 | if (!subdir.mkdir()) throw new RuntimeException("Cannot create directory: " + subdir.getPath()); 98 | } 99 | dir = subdir; 100 | } 101 | 102 | // Create class file 103 | File transformedClassFile = new File(dir, onlyName + ".class"); 104 | if (transformedClassFile.exists()) { 105 | if (!transformedClassFile.delete()) throw new RuntimeException("Cannot delete file: " + transformedClassFile.getPath()); 106 | } 107 | try { 108 | if (!transformedClassFile.createNewFile()) 109 | throw new RuntimeException("Cannot create new file: " + transformedClassFile.getPath()); 110 | } catch (IOException e) { 111 | throw new RuntimeException("Cannot create new file: " + transformedClassFile.getPath(), e); 112 | } 113 | 114 | try { 115 | Files.write(transformedClassFile.toPath(), transformResult.getData(), StandardOpenOption.WRITE); 116 | } catch (IOException e) { 117 | throw new RuntimeException("Cannot write to file: " + transformedClassFile.getPath(), e); 118 | } 119 | } 120 | 121 | } 122 | 123 | private String className; 124 | private boolean wasModified; // Были ли на самом деле изменения в классе 125 | 126 | public TransformResult transform(byte[] bytes) { 127 | className = null; 128 | wasModified = false; 129 | 130 | // В первую очередь пройдёмся по всем методам и поищем лямбды, помеченные @Async-аннотациями 131 | // Их нужно найти в первую очередь, чтобы потом использовать выпарсенные type-аннотации так, как будто 132 | // бы они были навешаны прямо на метод 133 | Map asyncLambdas = new HashMap<>(); 134 | ClassReader lambdasSearchReader = new ClassReader(bytes); 135 | lambdasSearchReader.accept(new LambdasSearchVisitor(asyncLambdas), 0); 136 | 137 | Map analyzeResults = new HashMap<>(); 138 | 139 | // Сначала посчитаем для каждого метода кол-во точек восстановления внутри него 140 | // Это необходимо для генерации кода switch в начале метода 141 | ClassReader countingReader = new ClassReader(bytes); 142 | countingReader.accept(new ClassVisitor(Opcodes.ASM5) { 143 | @Override 144 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 145 | // Сохраняем имя класса в field, а также определяем, реализует ли этот класс ICoroRunnable 146 | className = name; 147 | super.visit(version, access, name, signature, superName, interfaces); 148 | } 149 | 150 | @Override 151 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 152 | return new MethodAnalyzer(Opcodes.ASM5, access, className, name, desc, 153 | signature, exceptions, analyzeResults, classLoader, 154 | asyncLambdas.get(new MethodId(className, name, desc))); 155 | } 156 | }, 0); 157 | 158 | ClassReader reader = new ClassReader(bytes); 159 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS /*| ClassWriter.COMPUTE_FRAMES*/) { 160 | @Override 161 | protected String getCommonSuperClass(String type1, String type2) { 162 | try { 163 | return super.getCommonSuperClass(type1, type2); 164 | } catch (RuntimeException e) { 165 | // todo : убедиться в том, что всегда возвращать Object здесь - безопасно 166 | return "java/lang/Object"; 167 | } 168 | } 169 | }; 170 | ClassVisitor adapter = new ClassVisitor(Opcodes.ASM5, writer) { 171 | @Override 172 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 173 | MethodId methodId = new MethodId(className, name, desc); 174 | 175 | // Если метод не нужно инструментировать, ничего и не делаем 176 | boolean needInstrument = analyzeResults.containsKey(methodId); 177 | if (!needInstrument) 178 | return super.visitMethod(access, name, desc, signature, exceptions); 179 | 180 | MethodAnalyzeResult analyzeResult = analyzeResults.get(methodId); 181 | 182 | // Если внутри метода нет ни одной точки восстановления - также можем не инструментировать его 183 | if (analyzeResult.getRestorePointCallsCount() == 0) 184 | return super.visitMethod(access, name, desc, signature, exceptions); 185 | 186 | wasModified = true; 187 | 188 | return new MethodAdapter(Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions), 189 | analyzeResult, 190 | (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC, 191 | Type.getType(desc).getReturnType()); 192 | } 193 | }; 194 | try { 195 | reader.accept(adapter, 0); 196 | } catch (Exception e) { 197 | e.printStackTrace(); 198 | System.exit(-1); 199 | } 200 | 201 | if (wasModified) { 202 | byte[] transformed = writer.toByteArray(); 203 | return new TransformResult(true, className, transformed); 204 | } else { 205 | return new TransformResult(false, className, bytes); 206 | } 207 | } 208 | 209 | private static void collectClassFilesRecursively(List allClassFiles, 210 | File directory) { 211 | final File[] allFiles = directory.listFiles(); 212 | if (null == allFiles) return; // Skip incorrect dir 213 | for (File file : allFiles) { 214 | if (file.isFile()) { 215 | if (file.getName().endsWith(".class")) { 216 | allClassFiles.add(file); 217 | } 218 | } else if (file.isDirectory()) { 219 | collectClassFilesRecursively(allClassFiles, file); 220 | } 221 | } 222 | 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/MethodAnalyzer.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import org.objectweb.asm.*; 4 | import org.objectweb.asm.tree.AbstractInsnNode; 5 | import org.objectweb.asm.tree.AnnotationNode; 6 | import org.objectweb.asm.tree.LabelNode; 7 | import org.objectweb.asm.tree.MethodNode; 8 | import org.objectweb.asm.tree.analysis.*; 9 | import org.objectweb.asm.tree.analysis.Frame; 10 | 11 | import java.lang.annotation.Annotation; 12 | import java.util.*; 13 | 14 | import static java.util.stream.Collectors.toList; 15 | 16 | /** 17 | * Получает для переданного метода количество точек восстановления и два параллельных массива: 18 | * массив состояний фрейма и массив инструкций (каждой инструкции соответствует состояние фрейма). 19 | * 20 | * Собранные данные записываются в мапы только в случае, если метод помечен аннотацией @Async. 21 | * 22 | * @author elwood 23 | */ 24 | public class MethodAnalyzer extends MethodVisitor { 25 | private final MethodNode mn; 26 | private final String owner; 27 | private final MethodId methodId; 28 | 29 | private final ClassLoader classLoader; 30 | 31 | private List declaredRestorePoints; 32 | private boolean rootLambda; 33 | 34 | // "out parameters" 35 | private final Map resultMap; 36 | 37 | private int restorePointCalls = 0; 38 | private Set restorePoints; // Set of found restore points 39 | private Set unpatchableRestorePoints; // Set of restore points marked with unpatchable=true flag 40 | 41 | public MethodAnalyzer(int api, int access, String owner, String name, String desc, String signature, 42 | String[] exceptions, 43 | Map resultMap, 44 | ClassLoader classLoader, 45 | AsyncLambdaInfo asyncLambdaInfo) { // This parameter is passed when method is lambda 46 | super(api, new MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions)); 47 | // 48 | this.mn = (MethodNode) super.mv; 49 | this.owner = owner; 50 | this.classLoader = classLoader; 51 | // 52 | this.methodId = new MethodId(owner, name, desc); 53 | // output 54 | this.resultMap = resultMap; 55 | 56 | if (asyncLambdaInfo != null) { 57 | this.declaredRestorePoints = asyncLambdaInfo.getDeclaredRestorePoints(); 58 | 59 | // If lambda is ICoroRunnable, we consider it as Root Lambda (lambda, which will be called first) 60 | // If you want to use some ICoroRunnable lambda in non-root context, you can inherit interface 61 | // (INonRootCoroRunnable extends ICoroRunnable) and use it instead of ICoroRunnable. 62 | this.rootLambda = asyncLambdaInfo.getDesc().endsWith(")Lorg/jcoro/ICoroRunnable;"); 63 | } 64 | } 65 | 66 | @Override 67 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 68 | if ("Lorg/jcoro/Async;".equals(desc)) { 69 | return new AnnotationNode(Opcodes.ASM5, desc) { 70 | @Override 71 | public void visitEnd() { 72 | assert "value".equals(this.values.get(0)); 73 | List restorePoints = (List) this.values.get(1); 74 | declaredRestorePoints = Helpers.parseAwaitAnnotations(restorePoints); 75 | super.visitEnd(); 76 | } 77 | }; 78 | } else { 79 | return super.visitAnnotation(desc, visible); 80 | } 81 | } 82 | 83 | @Override 84 | public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { 85 | return super.visitTypeAnnotation(typeRef, typePath, desc, visible); 86 | } 87 | 88 | private Optional findInRestorePoints(MethodId callingMethodId) { 89 | if (declaredRestorePoints == null) return Optional.empty(); 90 | 91 | return declaredRestorePoints.stream().filter(restorePoint -> { 92 | boolean ownerEquals = restorePoint.owner().equals("") 93 | || restorePoint.owner().equals(callingMethodId.className); 94 | boolean nameEquals = restorePoint.value().equals(callingMethodId.methodName); 95 | boolean descEquals = "".equals(restorePoint.desc()) 96 | || restorePoint.desc().equals(callingMethodId.signature); 97 | return ownerEquals && nameEquals && descEquals; 98 | }).findFirst(); 99 | } 100 | 101 | @Override 102 | public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 103 | MethodId callingMethodId = new MethodId(owner, name, desc); 104 | final Optional restorePointOptional = findInRestorePoints(callingMethodId); 105 | if (restorePointOptional.isPresent()) { 106 | if (null == restorePoints) restorePoints = new HashSet<>(); 107 | restorePoints.add(callingMethodId); 108 | // 109 | if (!restorePointOptional.get().patchable()) { 110 | if (null == unpatchableRestorePoints) unpatchableRestorePoints = new HashSet<>(); 111 | unpatchableRestorePoints.add(callingMethodId); 112 | } 113 | // 114 | restorePointCalls++; 115 | } 116 | // 117 | super.visitMethodInsn(opcode, owner, name, desc, itf); 118 | } 119 | 120 | private static class LocalVarInfo { 121 | String name; 122 | String desc; 123 | String signature; 124 | Label start; 125 | Label end; 126 | int index; 127 | 128 | public LocalVarInfo(String name, String desc, String signature, Label start, Label end, int index) { 129 | this.name = name; 130 | this.desc = desc; 131 | this.signature = signature; 132 | this.start = start; 133 | this.end = end; 134 | this.index = index; 135 | } 136 | } 137 | 138 | private List localVars = new ArrayList<>(); 139 | 140 | @Override 141 | public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { 142 | localVars.add(new LocalVarInfo(name, desc, signature, start, end, index)); 143 | super.visitLocalVariable(name, desc, signature, start, end, index); 144 | } 145 | 146 | @Override 147 | public void visitEnd() { 148 | // Нужно ли записывать собранные данные в выходные мапы 149 | // Если на методе нет аннотации @Async - то не пишем 150 | // В противном случае пишем, даже если точек восстановления не найдено 151 | if (declaredRestorePoints == null) { 152 | super.visitEnd(); 153 | return; 154 | } 155 | 156 | Analyzer analyzer = new Analyzer(new SimpleVerifier() { 157 | @Override 158 | protected Class getClass(Type t) { 159 | try { 160 | return super.getClass(t); 161 | } catch (RuntimeException e) { 162 | try { 163 | // Используем класслоадер, который умеет загружать исходный класс из массива байт 164 | // Это нужно для работы верификатора 165 | return classLoader.loadClass(t.getInternalName().replaceAll("/", ".")); 166 | } catch (ClassNotFoundException e1) { 167 | throw new RuntimeException(e1); 168 | } 169 | } 170 | } 171 | }); 172 | AbstractInsnNode[] insns = mn.instructions.toArray(); 173 | Frame[] frames; 174 | try { 175 | frames = analyzer.analyze(owner, mn); 176 | 177 | // Корректируем инфу о типах значений локальных переменных с учётом таблиц переменных, 178 | // которые записываются в class-файл. Без этого ASM иногда не может определить настоящий тип 179 | // переменной (после инструкции aconst_null, например) и выдаёт в этом месте "Lnull;". 180 | // Нас это не устраивает, мы не можем обрабатывать переменных, типа которых мы не знаем. 181 | localVars.forEach(localVarInfo -> { 182 | LabelNode startLabel = (LabelNode) localVarInfo.start.info; // inclusive 183 | LabelNode endLabel = (LabelNode) localVarInfo.end.info; // exclusive 184 | boolean meetStart = false; 185 | boolean meetEnd = false; 186 | for (int i = 0; i < insns.length; i++) { 187 | if (insns[i] == startLabel) { 188 | meetStart = true; 189 | } 190 | if (insns[i] == endLabel) { 191 | meetEnd = true; 192 | } 193 | if (meetStart && !meetEnd) { 194 | Value oldLocal = frames[i].getLocal(localVarInfo.index); 195 | BasicValue newLocal = new BasicValue(Type.getType(localVarInfo.desc)); 196 | if (!((BasicValue) oldLocal).getType().getDescriptor().equals(localVarInfo.desc)) { 197 | frames[i].setLocal(localVarInfo.index, newLocal); 198 | } 199 | } 200 | } 201 | }); 202 | 203 | // Теперь окончательно проверяем, что фреймов с переменными типа "Lnull;" нигде не осталось 204 | // В стеке, к сожалению, "Lnull;" возможны (после инструкций типа ACONST_NULL) 205 | for (int i = 0; i < frames.length; i++) { 206 | final Frame frame = frames[i]; 207 | if (frame != null) { // frame может быть null в конце методов, если крайние инструкции - что-то вроде labels 208 | for (int j = 0; j < frame.getLocals(); j++) { 209 | final BasicValue value = (BasicValue) frame.getLocal(j); 210 | if (value.getType() != null && "Lnull;".equals(value.getType().getDescriptor())) 211 | throw new AssertionError("This shouldn't happen. Bug in ASM ?"); 212 | } 213 | } 214 | } 215 | } catch (AnalyzerException e) { 216 | throw new RuntimeException("Cannot analyze method", e); 217 | } 218 | // 219 | resultMap.put(methodId, new MethodAnalyzeResult( 220 | restorePointCalls, restorePoints, unpatchableRestorePoints, frames, insns, rootLambda) 221 | ); 222 | // 223 | super.visitEnd(); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /jcoro-api/src/main/java/org/jcoro/Coro.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * @author elwood 7 | */ 8 | @SuppressWarnings("unused") 9 | public class Coro implements AutoCloseable { 10 | private static ThreadLocal> activeCoroStack = new ThreadLocal<>(); 11 | 12 | private final ICoroRunnable runnable; 13 | 14 | private volatile Object memoryBarrier; 15 | 16 | private Coro(ICoroRunnable runnable) { 17 | this.runnable = runnable; 18 | } 19 | 20 | public static boolean exists() { 21 | return getSafe() != null; 22 | } 23 | 24 | /** 25 | * Returns top active coro instance or throws IllegalStateException 26 | * if there are no coro created yet. Should be called by user code if 27 | * need to retrieve current coro; 28 | */ 29 | public static Coro get() { 30 | final Stack coroStack = activeCoroStack.get(); 31 | if (null == coroStack || coroStack.empty()) 32 | throw new IllegalStateException("No active coro exists"); 33 | return coroStack.peek(); 34 | } 35 | 36 | /** 37 | * Returns top active coro instance or null if there are no coro created yet. 38 | * Called from generated code when instrumented method starts. 39 | */ 40 | public static Coro getSafe() { 41 | final Stack coroStack = activeCoroStack.get(); 42 | if (null == coroStack || coroStack.empty()) 43 | return null; 44 | return coroStack.peek(); 45 | } 46 | 47 | /** 48 | * Returns top active coro instance without checking of its existence. 49 | * Can be used only if caller is sure about coro exists. 50 | */ 51 | private static Coro getUnsafe() { 52 | return activeCoroStack.get().peek(); 53 | } 54 | 55 | private static void pushCoro(Coro coro) { 56 | Stack coroStack = activeCoroStack.get(); 57 | if (coroStack == null) { 58 | coroStack = new Stack<>(); 59 | activeCoroStack.set(coroStack); 60 | } 61 | coroStack.push(coro); 62 | } 63 | 64 | private boolean isYielding = false; 65 | 66 | public static Coro initSuspended(ICoroRunnable runnable) { 67 | return new Coro(runnable); 68 | } 69 | 70 | private Runnable deferFunc; 71 | 72 | private boolean suspendedAfterYield = false; 73 | 74 | /** 75 | * Приостанавливает выполнение сопрограммы, сохраняя состояние и возвращая дефолтные значения 76 | * вверх по всему стеку вызовов до корневого ICoroRunnable.run() - метода. 77 | */ 78 | public void yield() { 79 | if (isYielding) 80 | throw new IllegalStateException("Yielding is already started"); 81 | // suspendedAfterYield равен true, если мы восстанавливаем стек и в процессе восстановления стека 82 | // добрались до самого глубокого вызова yield() - на котором собственно и запаузились 83 | // И если мы находимся внутри yield() и suspendedAfterYield = true, то нам ничего делать не нужно 84 | // Только сбросить обратно этот флаг с тем, чтобы сопрограмму снова можно было запаузить 85 | if (suspendedAfterYield) { 86 | suspendedAfterYield = false; 87 | return; 88 | } 89 | isYielding = true; 90 | get().refsStack.push(this); // Аргументы и this если есть 91 | } 92 | 93 | public void setDeferFunc(Runnable deferFunc) { 94 | this.deferFunc = deferFunc; 95 | } 96 | 97 | /** 98 | * Приостанавливает выполнение сопрограммы, сохраняя состояние и возвращая дефолтные значения 99 | * вверх по всему стеку вызовов до корневого ICoroRunnable.run() - метода. Дополнительно задаёт 100 | * deferFunc, который будет выполнен после сохранения состояния всех методов. В качестве deferFunc 101 | * удобно передавать лямбду, которая шедулит очередную асинхронную операцию с коллбеком. 102 | */ 103 | public void yield(Runnable deferFunc) { 104 | // Так как мы не инструментируем этот метод (он - библиотечный), а вызов этого метода 105 | // является точкой восстановления (как и yield() без параметров), то 106 | // мы должны быть аккуратными в месте вызова этого метода, и не менять состояние this, 107 | // если мы оказались в этом методе при восстановлении стека (при вызове yield(null)). 108 | // Иначе при восстановлении стека можно неожиданно обнулить deferFunc, 109 | // а выше по стеку тоже был вызов resume() - например если одна асинхронная операция 110 | // выполняется в том же потоке, в котором была запланирована 111 | if (!suspendedAfterYield) { 112 | this.deferFunc = deferFunc; 113 | } 114 | yield(); 115 | } 116 | 117 | public void start() { 118 | resume(); 119 | } 120 | 121 | public void resume() { 122 | // If another thread will call resume() after yielding, it will read from volatile field, 123 | // and will see all changes made in memory before writing to field 124 | Object notUsed = this.memoryBarrier; 125 | 126 | pushCoro(this); 127 | try { 128 | // Call coro func 129 | if (suspendedAfterYield) { 130 | Object rootInstance = popRef(); 131 | if (rootInstance != runnable 132 | && rootInstance != null) // rootInstance is null when using lambdas (which are static methods actually) 133 | throw new AssertionError("This shouldn't happen"); 134 | } 135 | runnable.run(); 136 | } finally { 137 | if (isYielding) { 138 | isYielding = false; 139 | suspendedAfterYield = true; 140 | } 141 | activeCoroStack.get().pop(); 142 | } 143 | 144 | // Write to volatile field to set the memory barrier 145 | this.memoryBarrier = null; 146 | 147 | // Call defer func 148 | // Note that there are no guarantees that all changes in memory made by deferFunc will be seen by another 149 | // thread after resuming. If you need this, you should care about this explicitly. 150 | if (deferFunc != null) { 151 | // Обнуляем deferFunc перед вызовом, т.к. внутри deferFunc может быть любой код, 152 | // в том числе и приводящий к рекурсивному вызову resume() - например если 153 | // запланированная в deferFunc асинхронная операция выполняется мгновенно в вызывающем потоке 154 | // В этом случае при вызове coro,resume() из callback'a вложенной асинхронной операции 155 | // resume() увидит deferFunc, уже выполняющийся, и выполнит его ещё раз. Скорее всего, 156 | // это приведёт к тому, что крайний вызов будет лишним, а resume() выполнится ещё один раз, 157 | // когда уже не будет сохранённого state, и сопрограмма начнёт выполняться сначала. 158 | // В общем, произойдёт полное разрушение потока выполнения 159 | Runnable deferFuncCopy = deferFunc; 160 | deferFunc = null; 161 | deferFuncCopy.run(); 162 | } 163 | } 164 | 165 | private Stack statesStack = new Stack<>(); 166 | 167 | private Stack refsStack = new Stack<>(); 168 | private Stack intsStack = new Stack<>(); 169 | private Stack doublesStack = new Stack<>(); 170 | private Stack floatsStack = new Stack<>(); 171 | private Stack longsStack = new Stack<>(); 172 | 173 | public static void pushState(int state) { 174 | getUnsafe().statesStack.push(state); 175 | } 176 | 177 | public static void pushRef(Object ref) { 178 | getUnsafe().refsStack.push(ref); 179 | } 180 | 181 | public static void pushInt(int i) { 182 | getUnsafe().intsStack.push(i); 183 | } 184 | 185 | public static void pushDouble(double d) { 186 | getUnsafe().doublesStack.push(d); 187 | } 188 | 189 | public static void pushFloat(float f) { 190 | getUnsafe().floatsStack.push(f); 191 | } 192 | 193 | public static void pushLong(long l) { 194 | getUnsafe().longsStack.push(l); 195 | } 196 | 197 | public static Integer popState() { 198 | Stack statesStack = getUnsafe().statesStack; 199 | if (statesStack.empty()) return null; 200 | return statesStack.pop(); 201 | } 202 | 203 | public static Object popRef() { 204 | return getUnsafe().refsStack.pop(); 205 | } 206 | 207 | public static int popInt() { 208 | return getUnsafe().intsStack.pop(); 209 | } 210 | 211 | public static double popDouble() { 212 | return getUnsafe().doublesStack.pop(); 213 | } 214 | 215 | public static float popFloat() { 216 | return getUnsafe().floatsStack.pop(); 217 | } 218 | 219 | public static long popLong() { 220 | return getUnsafe().longsStack.pop(); 221 | } 222 | 223 | public static boolean isYielding() { 224 | final Coro coro = getSafe(); 225 | return coro != null && coro.isYielding; 226 | } 227 | 228 | // Stuff for support of storing args of methods before calling 229 | // (if dealing with unpatchable methods) 230 | // Здесь довольно много не очень хорошо поименованных методов, это следствие того, что 231 | // объектики при вызове unpatchable метода нужно неоднократно перекладывать между стеками 232 | 233 | /** 234 | * Флаг, устанавливается в true при вызове unpatchable метода при восстановлении контекста выполнения 235 | * (чтобы первый вызванный unpatchable-кодом инструментированный метод скорректировал refsStack, убрав 236 | * лишний this со стека (если он не статический), после этого сбросив флаг обратно в false). Если же первый 237 | * инструментированный метод - статический, то флаг просто должен быть сброшен в false. 238 | */ 239 | private boolean unpatchableCall; 240 | 241 | public static boolean isUnpatchableCall() { 242 | return getUnsafe().unpatchableCall; 243 | } 244 | 245 | public static void setUnpatchableCall(boolean unpatchableCall) { 246 | getUnsafe().unpatchableCall = unpatchableCall; 247 | } 248 | 249 | private static class UnpatchableMethodArgsStore { 250 | public Stack refsStack = new Stack<>(); 251 | public Stack intsStack = new Stack<>(); 252 | public Stack longsStack = new Stack<>(); 253 | public Stack floatsStack = new Stack<>(); 254 | public Stack doublesStack = new Stack<>(); 255 | } 256 | 257 | private UnpatchableMethodArgsStore unpatchableStore; 258 | 259 | private UnpatchableMethodArgsStore getUnpatchableStore() { 260 | if (null == unpatchableStore) 261 | unpatchableStore = new UnpatchableMethodArgsStore(); 262 | return unpatchableStore; 263 | } 264 | 265 | public static void pushRefToUnpatchable(Object ref) { 266 | getUnsafe().getUnpatchableStore().refsStack.push(ref); 267 | } 268 | 269 | public static void pushIntToUnpatchable(int i) { 270 | getUnsafe().getUnpatchableStore().intsStack.push(i); 271 | } 272 | 273 | public static void pushLongToUnpatchable(long l) { 274 | getUnsafe().getUnpatchableStore().longsStack.push(l); 275 | } 276 | 277 | public static void pushFloatToUnpatchable(float f) { 278 | getUnsafe().getUnpatchableStore().floatsStack.push(f); 279 | } 280 | 281 | public static void pushDoubleToUnpatchable(double d) { 282 | getUnsafe().getUnpatchableStore().doublesStack.push(d); 283 | } 284 | 285 | public static Object popRefFromUnpatchable() { 286 | return getUnsafe().getUnpatchableStore().refsStack.pop(); 287 | } 288 | 289 | public static int popIntFromUnpatchable() { 290 | return getUnsafe().getUnpatchableStore().intsStack.pop(); 291 | } 292 | 293 | public static long popLongFromUnpatchable() { 294 | return getUnsafe().getUnpatchableStore().longsStack.pop(); 295 | } 296 | 297 | public static float popFloatFromUnpatchable() { 298 | return getUnsafe().getUnpatchableStore().floatsStack.pop(); 299 | } 300 | 301 | public static double popDoubleFromUnpatchable() { 302 | return getUnsafe().getUnpatchableStore().doublesStack.pop(); 303 | } 304 | 305 | public static Object peekRefFromUnpatchable(int skip) { 306 | final Stack stack = getUnsafe().getUnpatchableStore().refsStack; 307 | return stack.get(stack.size() - 1 - skip); 308 | } 309 | 310 | public static int peekIntFromUnpatchable(int skip) { 311 | final Stack stack = getUnsafe().getUnpatchableStore().intsStack; 312 | return stack.get(stack.size() - 1 - skip); 313 | } 314 | 315 | public static long peekLongFromUnpatchable(int skip) { 316 | final Stack stack = getUnsafe().getUnpatchableStore().longsStack; 317 | return stack.get(stack.size() - 1 - skip); 318 | } 319 | 320 | public static float peekFloatFromUnpatchable(int skip) { 321 | final Stack stack = getUnsafe().getUnpatchableStore().floatsStack; 322 | return stack.get(stack.size() - 1 - skip); 323 | } 324 | 325 | public static double peekDoubleFromUnpatchable(int skip) { 326 | final Stack stack = getUnsafe().getUnpatchableStore().doublesStack; 327 | return stack.get(stack.size() - 1 - skip); 328 | } 329 | 330 | /** 331 | * Убирает с верхушки стека unpatchables сразу все аргументы фрейма. Нужно передать, сколько 332 | * объектов какого типа было сохранено. 333 | */ 334 | public static void cleanupUnpatchableFrame(int refs, int ints, int longs, int floats, int doubles) { 335 | final UnpatchableMethodArgsStore store = getUnsafe().getUnpatchableStore(); 336 | for (int i = 0; i < refs; i++) store.refsStack.pop(); 337 | for (int i = 0; i < ints; i++) store.intsStack.pop(); 338 | for (int i = 0; i < longs; i++) store.longsStack.pop(); 339 | for (int i = 0; i < floats; i++) store.floatsStack.pop(); 340 | for (int i = 0; i < doubles; i++) store.doublesStack.pop(); 341 | } 342 | 343 | public void close() throws Exception { 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /jcoro-agent/src/main/java/org/jcoro/MethodAdapter.java: -------------------------------------------------------------------------------- 1 | package org.jcoro; 2 | 3 | import org.objectweb.asm.*; 4 | import org.objectweb.asm.tree.AbstractInsnNode; 5 | import org.objectweb.asm.tree.analysis.BasicValue; 6 | import org.objectweb.asm.tree.analysis.Frame; 7 | import org.objectweb.asm.tree.analysis.Value; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * @author elwood 14 | */ 15 | public class MethodAdapter extends MethodVisitor { 16 | private final MethodAnalyzeResult analyzeResult; 17 | 18 | private final boolean isStatic; 19 | private final Type returnType; 20 | 21 | private int insnIndex = 0; // Currently monitoring index of original instruction 22 | private Label[] restoreLabels; 23 | private int restorePointsProcessed = 0; 24 | 25 | public MethodAdapter(int api, MethodVisitor mv, MethodAnalyzeResult methodAnalyzeResult, 26 | boolean isStatic, Type returnType) { 27 | super(api, mv); 28 | // 29 | this.analyzeResult = methodAnalyzeResult; 30 | this.isStatic = isStatic; 31 | this.returnType = returnType; 32 | } 33 | 34 | private Frame currentFrame() { 35 | return analyzeResult.getFrames()[insnIndex]; 36 | } 37 | 38 | private Frame nextFrame() { 39 | return analyzeResult.getFrames()[insnIndex + 1]; 40 | } 41 | 42 | private static class TryCatchBlock { 43 | Label start; 44 | Label end; 45 | Label handler; 46 | String type; 47 | 48 | TryCatchBlock(Label start, Label end, Label handler, String type) { 49 | this.start = start; 50 | this.end = end; 51 | this.handler = handler; 52 | this.type = type; 53 | } 54 | } 55 | 56 | // Все участки [label_1; label_2) должны быть исключены из всех блоков try-catch в методе. 57 | // 58 | // Дело в том, что при инструментировании методов участки [restorePointStart; noActiveCoro) и [afterCall; noSaveContext) 59 | // не должны быть внутри try-catch блоков. А собственно вызов (участок [noActiveCoro;afterCall) ) - должен быть в рамках try-catch 60 | // (если таковой имеется). То есть мы делим исходный try-catch на три - до нового байткода, собственно вызов, и после нового байткода. 61 | // 62 | // Чтобы хранить информацию о том, какие участки нужно исключить из try-catch блоков, используем эту структуру. 63 | private static class TryCatchExcludeBlock { 64 | Label label_1; 65 | Label label_2; 66 | } 67 | private List tryCatchBlocks = new ArrayList<>(); 68 | private List tryCatchExcludeBlocks = new ArrayList<>(); 69 | 70 | @Override 71 | public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 72 | tryCatchBlocks.add(new TryCatchBlock(start, end, handler, type)); 73 | } 74 | 75 | // Возвращает true и заполняет result блоками, на которые был разбит исходный блок, 76 | // Если блок не надо разбивать, возвращает false 77 | private boolean splitIfNeed(TryCatchBlock block, List result) { 78 | int start = block.start.getOffset(); 79 | int end = block.end.getOffset(); 80 | for (TryCatchExcludeBlock splitCandidate : tryCatchExcludeBlocks) { 81 | int label_1 = splitCandidate.label_1.getOffset(); 82 | int label_2 = splitCandidate.label_2.getOffset(); 83 | 84 | if (label_1 >= start && label_1 <= end && label_2 >= start && label_2 <= end) { 85 | // Интервал [label_1; label_2) находится внутри интервала [start; end) 86 | // Вырезаем его - остаётся 2 интервала [start; label1) и [label2; end) 87 | if (label_1 > start) 88 | result.add(new TryCatchBlock(block.start, splitCandidate.label_1, block.handler, block.type)); 89 | if (end > label_2) 90 | result.add(new TryCatchBlock(splitCandidate.label_2, block.end, block.handler, block.type)); 91 | return true; 92 | } else if (label_1 >= start && label_1 < end && label_2 >= end) { 93 | // Интервал [label_1; label_2) касается интервала [start; end) справа 94 | // Вырезаем его - остаётся один интервал [start; label1) 95 | if (label_1 > start) 96 | result.add(new TryCatchBlock(block.start, splitCandidate.label_1, block.handler, block.type)); 97 | return true; 98 | } else if (label_1 < start && label_2 > start && label_2 <= end) { 99 | // Интервал [label_1; label_2) касается интервала [start; end) слева 100 | // Вырезаем его - остаётся один интервал [label2; end) 101 | if (end > label_2) 102 | result.add(new TryCatchBlock(splitCandidate.label_2, block.end, block.handler, block.type)); 103 | return true; 104 | } else if (label_1 < start && label_2 >= end) { 105 | // Интервал [label_1; label_2) не должен охватывать целиком [start; end), 106 | // т.к. в генерируемом коде мы не добавляем своих try-catch блоков 107 | throw new AssertionError("This shouldn't happen"); 108 | } else { 109 | // Do nothing 110 | } 111 | } 112 | return false; 113 | } 114 | 115 | /** 116 | * Выполняет разбиение всех блоков try-catch до тех пор, пока разбивать станет нечего. 117 | */ 118 | private List splitTryCatchBlocks() { 119 | List blocksBefore = tryCatchBlocks; 120 | List blocksAfter; 121 | boolean anyChange; 122 | do { 123 | anyChange = false; 124 | blocksAfter = new ArrayList<>(); 125 | for (TryCatchBlock block : blocksBefore) { 126 | final List splittedBlocks = new ArrayList<>(); 127 | if (splitIfNeed(block, splittedBlocks)) { 128 | blocksAfter.addAll(splittedBlocks); 129 | anyChange = true; 130 | } else { 131 | blocksAfter.add(block); 132 | } 133 | } 134 | blocksBefore = blocksAfter; 135 | } while (anyChange); 136 | return blocksAfter; 137 | } 138 | 139 | @Override 140 | public void visitEnd() { 141 | final List splittedTryCatchBlocks = splitTryCatchBlocks(); 142 | for (TryCatchBlock tryCatchBlock : splittedTryCatchBlocks) { 143 | mv.visitTryCatchBlock(tryCatchBlock.start, tryCatchBlock.end, tryCatchBlock.handler, tryCatchBlock.type); 144 | } 145 | super.visitEnd(); 146 | } 147 | 148 | @Override 149 | public void visitCode() { 150 | // if (Coro.get() == null) goto noActiveCoroLabel; 151 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "getSafe", "()Lorg/jcoro/Coro;", false); 152 | Label noActiveCoroLabel = new Label(); 153 | mv.visitJumpInsn(Opcodes.IFNULL, noActiveCoroLabel); 154 | 155 | // popState() 156 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popState", "()Ljava/lang/Integer;", false); 157 | mv.visitInsn(Opcodes.DUP); 158 | Label noActiveStateLabel = new Label(); 159 | mv.visitJumpInsn(Opcodes.IFNULL, noActiveStateLabel); 160 | 161 | // Now, when state != null, we have to pop extra ref (saved "this") if current method is not static 162 | // and if this method has been called from unpatchable context 163 | if (!isStatic) { 164 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "isUnpatchableCall", "()Z", false); 165 | Label noUnpatchableFlag = new Label(); 166 | mv.visitJumpInsn(Opcodes.IFEQ, noUnpatchableFlag); 167 | 168 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popRef", "()Ljava/lang/Object;", false); 169 | mv.visitInsn(Opcodes.POP); 170 | 171 | mv.visitLabel(noUnpatchableFlag); 172 | putFrame(currentFrame(), "Ljava/lang/Integer;"); 173 | } 174 | // Reset Coro.unpatchableCall flag to false always (when restoring state) 175 | mv.visitLdcInsn(false); 176 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "setUnpatchableCall", "(Z)V", false); 177 | 178 | // switch (state) 179 | final int nRestorePoints = analyzeResult.getRestorePointCallsCount(); 180 | restoreLabels = new Label[nRestorePoints]; 181 | for (int i = 0; i < nRestorePoints; i++) restoreLabels[i] = new Label(); 182 | if (nRestorePoints == 1) { 183 | // If state != null and there is only one restore point in this method, jump to label directly 184 | mv.visitInsn(Opcodes.POP); 185 | mv.visitJumpInsn(Opcodes.GOTO, restoreLabels[0]); 186 | } else { 187 | assert nRestorePoints > 1; 188 | mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Ljava/lang/Integer;", "intValue", "()I", false); 189 | mv.visitTableSwitchInsn(0, nRestorePoints - 1, noActiveCoroLabel, restoreLabels); 190 | } 191 | 192 | // noActiveStateLabel: 193 | mv.visitLabel(noActiveStateLabel); 194 | putFrame(currentFrame(), "Ljava/lang/Integer;"); 195 | mv.visitInsn(Opcodes.POP); // Remove extra (duplicated) `state` 196 | 197 | // noActiveCoroLabel: 198 | mv.visitLabel(noActiveCoroLabel); 199 | putFrame(currentFrame()); 200 | super.visitCode(); 201 | } 202 | 203 | private Object convertFrameOperandToInsn(Value value) { 204 | final BasicValue local = (BasicValue) value; 205 | if (local.isReference()) { 206 | return local.getType().getDescriptor(); 207 | } else if (local == BasicValue.RETURNADDRESS_VALUE) { 208 | // Эта штука возможна только в старый версиях джавы - когда в рамках метода можно было 209 | // делать подпрограммы (см инструкции jsr и ret) - после выхода джавы 1.6 это уже не актуально 210 | throw new UnsupportedOperationException("Frames with old opcodes (subroutines) are not supported"); 211 | } else if (local == BasicValue.UNINITIALIZED_VALUE) { 212 | return Opcodes.TOP; 213 | } else { 214 | final int sort = local.getType().getSort(); 215 | switch (sort) { 216 | case Type.VOID: 217 | case Type.OBJECT: 218 | case Type.ARRAY: 219 | // Should be already processed in if (isReference()) case 220 | throw new AssertionError("This shouldn't happen"); 221 | case Type.INT: 222 | case Type.SHORT: 223 | case Type.BYTE: 224 | case Type.BOOLEAN: 225 | case Type.CHAR: 226 | return Opcodes.INTEGER; 227 | case Type.LONG: 228 | return Opcodes.LONG; 229 | case Type.DOUBLE: 230 | return Opcodes.DOUBLE; 231 | case Type.FLOAT: 232 | return Opcodes.FLOAT; 233 | default: 234 | throw new AssertionError("This shouldn't happen"); 235 | } 236 | } 237 | } 238 | 239 | private void putFrame(Frame frame) { 240 | putFrame(frame, null); 241 | } 242 | 243 | private void putFrame(Frame frame, String additionalStackOperand) { 244 | Object[] locals = new Object[frame.getLocals()]; 245 | for (int i = 0; i < frame.getLocals(); i++) { 246 | locals[i] = convertFrameOperandToInsn(frame.getLocal(i)); 247 | } 248 | Object[] stacks = new Object[frame.getStackSize() + (additionalStackOperand != null ? 1 : 0)]; 249 | for (int i = 0; i < frame.getStackSize(); i++) { 250 | stacks[i] = convertFrameOperandToInsn(frame.getStack(i)); 251 | } 252 | if (additionalStackOperand != null) { 253 | stacks[stacks.length - 1] = additionalStackOperand; 254 | } 255 | Object[] fixedLocals = fixLocals(locals); 256 | callVisitFrame(Opcodes.F_FULL, fixedLocals.length, fixedLocals, stacks.length, stacks); 257 | } 258 | 259 | private void visitCurrentFrameWithoutStack() { 260 | // Если метод статический - все локальные переменные еще равны TOP 261 | // Если метод нестатический - первая переменная - this, остальное - TOP 262 | final Frame frame = currentFrame(); 263 | Object[] locals = new Object[frame.getLocals()]; 264 | int i = 0; 265 | if (!isStatic) { 266 | locals[0] = convertFrameOperandToInsn(frame.getLocal(0)); 267 | i++; 268 | } 269 | for (int j = i; j < locals.length; j++) { 270 | locals[j] = Opcodes.TOP; 271 | } 272 | Object[] fixedLocals = fixLocals(locals); 273 | callVisitFrame(Opcodes.F_FULL, fixedLocals.length, fixLocals(locals), 0, new Object[0]); 274 | } 275 | 276 | private void callVisitFrame(int type, int nLocal, Object[] local, int nStack, 277 | Object[] stack) { 278 | mv.visitFrame(type, nLocal, local, nStack, stack); 279 | noInsnsSinceLastFrame = true; 280 | } 281 | 282 | private Object[] fixLocals(Object[] locals) { 283 | List fixed = new ArrayList<>(); 284 | for (int i = 0; i < locals.length; i++) { 285 | fixed.add(locals[i]); 286 | if (locals[i] == Opcodes.DOUBLE || locals[i] == Opcodes.LONG) { 287 | i++; // skip next 288 | } 289 | } 290 | return fixed.toArray(new Object[fixed.size()]); 291 | } 292 | 293 | /** 294 | * Generates ldc 0 (null) instruction for specified type. 295 | */ 296 | private void visitLdcDefaultValueForType(Type type) { 297 | final int sort = type.getSort(); 298 | switch (sort) { 299 | case Type.VOID: 300 | break; 301 | case Type.OBJECT: 302 | case Type.ARRAY: 303 | mv.visitInsn(Opcodes.ACONST_NULL); 304 | break; 305 | case Type.INT: 306 | case Type.SHORT: 307 | case Type.BYTE: 308 | case Type.BOOLEAN: 309 | case Type.CHAR: 310 | mv.visitLdcInsn(0); 311 | break; 312 | case Type.LONG: 313 | mv.visitLdcInsn(0L); 314 | break; 315 | case Type.DOUBLE: 316 | mv.visitLdcInsn(0.d); 317 | break; 318 | case Type.FLOAT: 319 | mv.visitLdcInsn(0.f); 320 | break; 321 | default: 322 | throw new AssertionError("This shouldn't happen"); 323 | } 324 | } 325 | 326 | private void restoreLocals() { 327 | Frame frame = currentFrame(); 328 | for (int i = frame.getLocals() - 1; i >= 0; i--) { 329 | BasicValue local = (BasicValue) frame.getLocal(i); 330 | if (local == BasicValue.UNINITIALIZED_VALUE) { 331 | // do nothing 332 | } else if (local == BasicValue.RETURNADDRESS_VALUE) { 333 | // do nothing 334 | } else if (local.isReference()) { 335 | final String typeDescriptor = local.getType().getDescriptor(); 336 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popRef", "()Ljava/lang/Object;", false); 337 | mv.visitTypeInsn(Opcodes.CHECKCAST, typeDescriptor); 338 | mv.visitVarInsn(Opcodes.ASTORE, i); 339 | } else { 340 | final int sort = local.getType().getSort(); 341 | switch (sort) { 342 | case Type.VOID: 343 | case Type.OBJECT: 344 | case Type.ARRAY: 345 | // Should be already processed in if (isReference()) case 346 | throw new AssertionError("This shouldn't happen"); 347 | case Type.INT: 348 | case Type.SHORT: 349 | case Type.BYTE: 350 | case Type.BOOLEAN: 351 | case Type.CHAR: 352 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popInt", "()I", false); 353 | mv.visitVarInsn(Opcodes.ISTORE, i); 354 | break; 355 | case Type.LONG: 356 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popLong", "()J", false); 357 | mv.visitVarInsn(Opcodes.LSTORE, i); 358 | break; 359 | case Type.DOUBLE: 360 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popDouble", "()D", false); 361 | mv.visitVarInsn(Opcodes.DSTORE, i); 362 | break; 363 | case Type.FLOAT: 364 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popFloat", "()F", false); 365 | mv.visitVarInsn(Opcodes.FSTORE, i); 366 | break; 367 | default: 368 | throw new AssertionError("This shouldn't happen"); 369 | } 370 | } 371 | } 372 | } 373 | 374 | private void restoreStackBottom(Type callingMethodType, boolean callingMethodIsStatic) { 375 | final Type[] argumentTypes = callingMethodType.getArgumentTypes(); 376 | int nArgs = argumentTypes.length; 377 | int skipStackVars = nArgs + ((!callingMethodIsStatic) ? 1 : 0); 378 | // 379 | Frame frame = currentFrame(); 380 | for (int i = frame.getStackSize() - 1; i >= skipStackVars; i--) { 381 | BasicValue local = (BasicValue) frame.getStack(i); 382 | if (local == BasicValue.UNINITIALIZED_VALUE) { 383 | // do nothing 384 | } else if (local == BasicValue.RETURNADDRESS_VALUE) { 385 | // do nothing 386 | } else if (local.isReference()) { 387 | final String typeDescriptor = local.getType().getDescriptor(); 388 | if (!"Lnull;".equals(typeDescriptor)) { 389 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popRef", "()Ljava/lang/Object;", false); 390 | mv.visitTypeInsn(Opcodes.CHECKCAST, typeDescriptor); 391 | } else 392 | mv.visitInsn(Opcodes.ACONST_NULL); 393 | } else { 394 | final int sort = local.getType().getSort(); 395 | switch (sort) { 396 | case Type.VOID: 397 | case Type.OBJECT: 398 | case Type.ARRAY: 399 | // Should be already processed in if (isReference()) case 400 | throw new AssertionError("This shouldn't happen"); 401 | case Type.INT: 402 | case Type.SHORT: 403 | case Type.BYTE: 404 | case Type.BOOLEAN: 405 | case Type.CHAR: 406 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popInt", "()I", false); 407 | break; 408 | case Type.LONG: 409 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popLong", "()J", false); 410 | break; 411 | case Type.DOUBLE: 412 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popDouble", "()D", false); 413 | break; 414 | case Type.FLOAT: 415 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popFloat", "()F", false); 416 | break; 417 | default: 418 | throw new AssertionError("This shouldn't happen"); 419 | } 420 | } 421 | } 422 | } 423 | 424 | private void restoreInstance(Type callingMethodType) { 425 | Frame frame = currentFrame(); 426 | // 427 | final Type[] argumentTypes = callingMethodType.getArgumentTypes(); 428 | int nArgs = argumentTypes.length; 429 | // 430 | BasicValue value = (BasicValue) frame.getStack(frame.getStackSize() - 1 - nArgs); 431 | if (!value.isReference()) throw new AssertionError("This shouldn't happen"); 432 | 433 | final String typeDescriptor = value.getType().getDescriptor(); 434 | if ("Lnull;".equals(typeDescriptor)) 435 | throw new AssertionError("This shouldn't happen"); 436 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popRef", "()Ljava/lang/Object;", false); 437 | mv.visitTypeInsn(Opcodes.CHECKCAST, typeDescriptor); 438 | } 439 | 440 | private void saveStackBottom(Type callingMethodReturnType) { 441 | Frame frame = nextFrame(); 442 | // Кроме возвращаемого значения вызванного метода - ведь он вернул нам null или 0 в случае 443 | // после осуществления прерывания 444 | boolean skipFirstStackItem = (callingMethodReturnType.getSort() != Type.VOID); 445 | // 446 | for (int i = skipFirstStackItem ? 1 : 0; i < frame.getStackSize(); i++) { 447 | BasicValue local = (BasicValue) frame.getStack(i); 448 | if (local == BasicValue.UNINITIALIZED_VALUE) { 449 | // do nothing 450 | } else if (local == BasicValue.RETURNADDRESS_VALUE) { 451 | // do nothing 452 | } else if (local.isReference()) { 453 | // Если здесь - null, то можно ничего не сохранять, а при восстановлении симметрично сделать ACONST_NULL 454 | final String typeDescriptor = local.getType().getDescriptor(); 455 | if (!"Lnull;".equals(typeDescriptor)) 456 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushRef", "(Ljava/lang/Object;)V", false); 457 | } else { 458 | final int sort = local.getType().getSort(); 459 | switch (sort) { 460 | case Type.VOID: 461 | case Type.OBJECT: 462 | case Type.ARRAY: 463 | // Should be already processed in if (isReference()) case 464 | throw new AssertionError("This shouldn't happen"); 465 | case Type.INT: 466 | case Type.SHORT: 467 | case Type.BYTE: 468 | case Type.BOOLEAN: 469 | case Type.CHAR: 470 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushInt", "(I)V", false); 471 | break; 472 | case Type.LONG: 473 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushLong", "(J)V", false); 474 | break; 475 | case Type.DOUBLE: 476 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushDouble", "(D)V", false); 477 | break; 478 | case Type.FLOAT: 479 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushFloat", "(F)V", false); 480 | break; 481 | default: 482 | throw new AssertionError("This shouldn't happen"); 483 | } 484 | } 485 | } 486 | } 487 | 488 | private void saveLocals() { 489 | Frame frame = nextFrame(); 490 | for (int i = 0; i = 1; // At least one local ("this") should be present 537 | mv.visitVarInsn(Opcodes.ALOAD, 0); 538 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushRef", "(Ljava/lang/Object;)V", false); 539 | } else if (analyzeResult.isRootLambda()) { 540 | // Put extra NULL object to keep stack balanced when resuming 541 | mv.visitInsn(Opcodes.ACONST_NULL); 542 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushRef", "(Ljava/lang/Object;)V", false); 543 | } 544 | } 545 | 546 | private void returnDefault() { 547 | visitLdcDefaultValueForType(returnType); // Push default value for return type 548 | // 549 | final int type = returnType.getSort(); 550 | switch (type) { 551 | case Type.VOID: 552 | mv.visitInsn(Opcodes.RETURN); 553 | break; 554 | case Type.OBJECT: 555 | case Type.ARRAY: 556 | mv.visitInsn(Opcodes.ARETURN); 557 | break; 558 | // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.9.2 559 | case Type.INT: 560 | case Type.SHORT: 561 | case Type.BYTE: 562 | case Type.BOOLEAN: 563 | case Type.CHAR: 564 | mv.visitInsn(Opcodes.IRETURN); 565 | break; 566 | case Type.LONG: 567 | mv.visitInsn(Opcodes.LRETURN); 568 | break; 569 | case Type.DOUBLE: 570 | mv.visitInsn(Opcodes.DRETURN); 571 | break; 572 | case Type.FLOAT: 573 | mv.visitInsn(Opcodes.FRETURN); 574 | break; 575 | default: 576 | throw new AssertionError("This shouldn't happen"); 577 | } 578 | } 579 | 580 | private void visitMethodInsnPatchable(int opcode, String owner, String name, String desc, boolean itf) { 581 | // Первый блок, который должен быть исключён из всех try-catch блоков метода 582 | // Блок обрамляет код восстановление контекста выполнения (последовательность pop-вызовов) 583 | TryCatchExcludeBlock tryCatchSplitInfo_1 = new TryCatchExcludeBlock(); 584 | // Второй блок - обрамляет код сохранения контекста выполнения (последовательность push-вызовов) 585 | TryCatchExcludeBlock tryCatchSplitInfo_2 = new TryCatchExcludeBlock(); 586 | 587 | Label noActiveCoroLabel = new Label(); 588 | tryCatchSplitInfo_1.label_2 = noActiveCoroLabel; 589 | mv.visitJumpInsn(Opcodes.GOTO, noActiveCoroLabel); 590 | 591 | // label_i: 592 | mv.visitLabel(restoreLabels[restorePointsProcessed]); 593 | visitCurrentFrameWithoutStack(); 594 | tryCatchSplitInfo_1.label_1 = restoreLabels[restorePointsProcessed]; 595 | 596 | // Restore execution context 597 | { 598 | boolean callingMethodIsStatic = (opcode == Opcodes.INVOKESTATIC); 599 | final Type callingMethodType = Type.getType(desc); 600 | 601 | restoreLocals(); 602 | 603 | // Восстанавливаем дно стека (стек операндов, за исключением аргументов подготавливаемого вызова) 604 | restoreStackBottom(callingMethodType, callingMethodIsStatic); 605 | 606 | // Восстанавливаем instance для вызова, если метод - экземплярный 607 | if (!callingMethodIsStatic) restoreInstance(callingMethodType); 608 | 609 | // Передаём дефолтные значения для аргументов вызова 610 | final Type[] argumentTypes = callingMethodType.getArgumentTypes(); 611 | for (Type argumentType : argumentTypes) { 612 | visitLdcDefaultValueForType(argumentType); 613 | } 614 | } 615 | 616 | // Сюда приходим сразу, если нет необходимости восстанавливать стек 617 | mv.visitLabel(noActiveCoroLabel); 618 | putFrame(currentFrame()); 619 | 620 | // Original call 621 | super.visitMethodInsn(opcode, owner, name, desc, itf); 622 | 623 | Label afterCallLabel = new Label(); 624 | mv.visitLabel(afterCallLabel); 625 | tryCatchSplitInfo_2.label_1 = afterCallLabel; 626 | 627 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "isYielding", "()Z", false); 628 | Label noSaveContextLabel = new Label(); 629 | tryCatchSplitInfo_2.label_2 = noSaveContextLabel; 630 | mv.visitJumpInsn(Opcodes.IFEQ, noSaveContextLabel); 631 | 632 | // Save execution context 633 | { 634 | // Save stack first 635 | saveStackBottom(Type.getReturnType(desc)); 636 | 637 | // Second save locals 638 | saveLocals(); 639 | 640 | // Finally, save "this" if method is instance method 641 | saveThis(); 642 | 643 | // Save the state 644 | mv.visitLdcInsn(restorePointsProcessed); 645 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushState", "(I)V", false); 646 | 647 | // And return 0 or null 648 | returnDefault(); 649 | } 650 | 651 | mv.visitLabel(noSaveContextLabel); 652 | putFrame(nextFrame()); 653 | 654 | tryCatchExcludeBlocks.add(tryCatchSplitInfo_1); 655 | tryCatchExcludeBlocks.add(tryCatchSplitInfo_2); 656 | restorePointsProcessed++; 657 | } 658 | 659 | /** 660 | * Restores args of calling type (for unpatchable call they were saved too). 661 | */ 662 | private void restoreArgs(Type callingMethodType) { 663 | final Type[] argumentTypes = callingMethodType.getArgumentTypes(); 664 | for (Type argumentType : argumentTypes) { 665 | final int sort = argumentType.getSort(); 666 | switch (sort) { 667 | case Type.VOID: 668 | case Type.OBJECT: 669 | case Type.ARRAY: 670 | final String typeDescriptor = argumentType.getDescriptor(); 671 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popRef", "()Ljava/lang/Object;", false); 672 | mv.visitTypeInsn(Opcodes.CHECKCAST, typeDescriptor); 673 | break; 674 | case Type.INT: 675 | case Type.SHORT: 676 | case Type.BYTE: 677 | case Type.BOOLEAN: 678 | case Type.CHAR: 679 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popInt", "()I", false); 680 | break; 681 | case Type.LONG: 682 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popLong", "()J", false); 683 | break; 684 | case Type.DOUBLE: 685 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popDouble", "()D", false); 686 | break; 687 | case Type.FLOAT: 688 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "popFloat", "()F", false); 689 | break; 690 | default: 691 | throw new AssertionError("This shouldn't happen"); 692 | } 693 | } 694 | } 695 | 696 | /** 697 | * Each argument for calling method should be copied into temporary unpatchable storage. 698 | * Unpatchable storage is represented by 5 stacks: refs, ints, longs, floats and doubles. 699 | * After copying we should restore original args set in the frame stack (to call the method). 700 | * To do this, we can use peekSomethingFromUnpatchable(int offset) method, and we need to know, 701 | * where concrete argument is placed inside one of unpatchable stack. This function returns 702 | * array of offsets that can be used to retrieve args back. 703 | * 704 | * offsets[0] corresponds to first argument 705 | * last offsets item corresponds to instance of object to call (if method is not static) 706 | * 707 | * For example to retrieve the first arg, call peekRefFromUnpatchable(offsets[0]). 708 | */ 709 | private int[] calculateUnpatchableOffsets(Type[] argumentTypes, boolean callingMethodIsStatic) { 710 | int[] offsets = new int[argumentTypes.length + (callingMethodIsStatic ? 0 : 1)]; 711 | StackDepthInfo stackDepth = calculateStackDepth(argumentTypes, callingMethodIsStatic); 712 | 713 | int _refsStackDepth = stackDepth.refsStackDepth; 714 | int _intsStackDepth = stackDepth.intsStackDepth; 715 | int _longsStackDepth = stackDepth.longsStackDepth; 716 | int _floatsStackDepth = stackDepth.floatsStackDepth; 717 | int _doublesStackDepth = stackDepth.doublesStackDepth; 718 | 719 | for (int i = argumentTypes.length - 1; i >= 0; i--) { 720 | Type argumentType = argumentTypes[i]; 721 | final int sort = argumentType.getSort(); 722 | switch (sort) { 723 | case Type.VOID: 724 | case Type.OBJECT: 725 | case Type.ARRAY: 726 | offsets[i] = --_refsStackDepth; 727 | break; 728 | case Type.INT: 729 | case Type.SHORT: 730 | case Type.BYTE: 731 | case Type.BOOLEAN: 732 | case Type.CHAR: 733 | offsets[i] = --_intsStackDepth; 734 | break; 735 | case Type.LONG: 736 | offsets[i] = --_longsStackDepth; 737 | break; 738 | case Type.FLOAT: 739 | offsets[i] = --_floatsStackDepth; 740 | break; 741 | case Type.DOUBLE: 742 | offsets[i] = --_doublesStackDepth; 743 | break; 744 | default: 745 | throw new AssertionError("This shouldn't happen"); 746 | } 747 | } 748 | return offsets; 749 | } 750 | 751 | /** 752 | * Saves the args in Coro's main storage, by copying them from unpatchable storage. 753 | */ 754 | private void saveArgs(Type[] argumentTypes, int[] offsets, boolean callingMethodIsStatic) { 755 | // Перекладываем аргументы из unpatchable storage в обычный storage 756 | for (int i = 0; i < argumentTypes.length; i++) { 757 | Type argumentType = argumentTypes[i]; 758 | final int sort = argumentType.getSort(); 759 | // 760 | mv.visitLdcInsn(offsets[i]); 761 | // 762 | switch (sort) { 763 | case Type.VOID: 764 | case Type.OBJECT: 765 | case Type.ARRAY: 766 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekRefFromUnpatchable", "(I)Ljava/lang/Object;", false); 767 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushRef", "(Ljava/lang/Object;)V", false); 768 | break; 769 | case Type.INT: 770 | case Type.SHORT: 771 | case Type.BYTE: 772 | case Type.BOOLEAN: 773 | case Type.CHAR: 774 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekIntFromUnpatchable", "(I)I", false); 775 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushInt", "(I)V", false); 776 | break; 777 | case Type.LONG: 778 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekLongFromUnpatchable", "(I)J", false); 779 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushLong", "(J)V", false); 780 | break; 781 | case Type.FLOAT: 782 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekFloatFromUnpatchable", "(I)F", false); 783 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushFloat", "(F)V", false); 784 | break; 785 | case Type.DOUBLE: 786 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekDoubleFromUnpatchable", "(I)D", false); 787 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushDouble", "(D)V", false); 788 | break; 789 | default: 790 | throw new AssertionError("This shouldn't happen"); 791 | } 792 | } 793 | 794 | if (!callingMethodIsStatic) { 795 | mv.visitLdcInsn(0); 796 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekRefFromUnpatchable", "(I)Ljava/lang/Object;", false); 797 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushRef", "(Ljava/lang/Object;)V", false); 798 | } 799 | } 800 | 801 | private static class StackDepthInfo { 802 | int refsStackDepth; 803 | int intsStackDepth; 804 | int longsStackDepth; 805 | int floatsStackDepth; 806 | int doublesStackDepth; 807 | } 808 | 809 | private static StackDepthInfo calculateStackDepth(Type[] argumentTypes, boolean callingMethodIsStatic) { 810 | StackDepthInfo info = new StackDepthInfo(); 811 | 812 | for (Type argumentType : argumentTypes) { 813 | final int sort = argumentType.getSort(); 814 | switch (sort) { 815 | case Type.VOID: 816 | case Type.OBJECT: 817 | case Type.ARRAY: 818 | info.refsStackDepth++; 819 | break; 820 | case Type.INT: 821 | case Type.SHORT: 822 | case Type.BYTE: 823 | case Type.BOOLEAN: 824 | case Type.CHAR: 825 | info.intsStackDepth++; 826 | break; 827 | case Type.LONG: 828 | info.longsStackDepth++; 829 | break; 830 | case Type.FLOAT: 831 | info.floatsStackDepth++; 832 | break; 833 | case Type.DOUBLE: 834 | info.doublesStackDepth++; 835 | break; 836 | default: 837 | throw new AssertionError("This shouldn't happen"); 838 | } 839 | } 840 | if (!callingMethodIsStatic) { 841 | info.refsStackDepth++; 842 | } 843 | 844 | return info; 845 | } 846 | 847 | /** 848 | * Removes all items saved to unpatchables storage before method call. 849 | */ 850 | private void cleanUnpatchablesFrame(Type[] argumentTypes, boolean callingMethodIsStatic) { 851 | StackDepthInfo stackDepth = calculateStackDepth(argumentTypes, callingMethodIsStatic); 852 | 853 | mv.visitLdcInsn(stackDepth.refsStackDepth); 854 | mv.visitLdcInsn(stackDepth.intsStackDepth); 855 | mv.visitLdcInsn(stackDepth.longsStackDepth); 856 | mv.visitLdcInsn(stackDepth.floatsStackDepth); 857 | mv.visitLdcInsn(stackDepth.doublesStackDepth); 858 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "cleanupUnpatchableFrame", "(IIIII)V", false); 859 | } 860 | 861 | private void saveArgsInTempStorage(Type[] argumentTypes, boolean callingMethodIsStatic, 862 | int[] offsets, String owner) { 863 | // Save args and instance to temporary storage 864 | for (int i = argumentTypes.length - 1; i >= 0; i--) { 865 | Type argumentType = argumentTypes[i]; 866 | final int sort = argumentType.getSort(); 867 | switch (sort) { 868 | case Type.VOID: 869 | case Type.OBJECT: 870 | case Type.ARRAY: 871 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushRefToUnpatchable", "(Ljava/lang/Object;)V", false); 872 | break; 873 | case Type.INT: 874 | case Type.SHORT: 875 | case Type.BYTE: 876 | case Type.BOOLEAN: 877 | case Type.CHAR: 878 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushIntToUnpatchable", "(I)V", false); 879 | break; 880 | case Type.LONG: 881 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushLongToUnpatchable", "(J)V", false); 882 | break; 883 | case Type.FLOAT: 884 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushFloatToUnpatchable", "(F)V", false); 885 | break; 886 | case Type.DOUBLE: 887 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushDoubleToUnpatchable", "(D)V", false); 888 | break; 889 | default: 890 | throw new AssertionError("This shouldn't happen"); 891 | } 892 | } 893 | if (!callingMethodIsStatic) { 894 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushRefToUnpatchable", "(Ljava/lang/Object;)V", false); 895 | } 896 | 897 | // Copy instance and args back to the frame stack 898 | if (!callingMethodIsStatic) { 899 | mv.visitLdcInsn(0); 900 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekRefFromUnpatchable", "(I)Ljava/lang/Object;", false); 901 | mv.visitTypeInsn(Opcodes.CHECKCAST, owner); 902 | } 903 | for (int i = 0; i < argumentTypes.length; i++) { 904 | Type argumentType = argumentTypes[i]; 905 | final int sort = argumentType.getSort(); 906 | // 907 | mv.visitLdcInsn(offsets[i]); 908 | // 909 | switch (sort) { 910 | case Type.VOID: 911 | case Type.OBJECT: 912 | case Type.ARRAY: 913 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekRefFromUnpatchable", "(I)Ljava/lang/Object;", false); 914 | mv.visitTypeInsn(Opcodes.CHECKCAST, argumentType.getDescriptor()); 915 | break; 916 | case Type.INT: 917 | case Type.SHORT: 918 | case Type.BYTE: 919 | case Type.BOOLEAN: 920 | case Type.CHAR: 921 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekIntFromUnpatchable", "(I)I", false); 922 | break; 923 | case Type.LONG: 924 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekLongFromUnpatchable", "(I)J", false); 925 | break; 926 | case Type.FLOAT: 927 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekFloatFromUnpatchable", "(I)F", false); 928 | break; 929 | case Type.DOUBLE: 930 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "peekDoubleFromUnpatchable", "(I)D", false); 931 | break; 932 | default: 933 | throw new AssertionError("This shouldn't happen"); 934 | } 935 | } 936 | } 937 | 938 | private void visitMethodInsnUnpatchable(int opcode, String owner, String name, String desc, boolean itf) { 939 | TryCatchExcludeBlock excludeBlock = new TryCatchExcludeBlock(); 940 | 941 | Label noRestoringLabel = new Label(); 942 | mv.visitJumpInsn(Opcodes.GOTO, noRestoringLabel); 943 | 944 | // label_i: 945 | mv.visitLabel(restoreLabels[restorePointsProcessed]); 946 | 947 | excludeBlock.label_1 = restoreLabels[restorePointsProcessed]; 948 | 949 | visitCurrentFrameWithoutStack(); 950 | 951 | // Restore execution context 952 | { 953 | boolean callingMethodIsStatic = (opcode == Opcodes.INVOKESTATIC); 954 | final Type callingMethodType = Type.getType(desc); 955 | 956 | restoreLocals(); 957 | 958 | // Восстанавливаем дно стека 959 | restoreStackBottom(callingMethodType, callingMethodIsStatic); 960 | 961 | // Восстанавливаем instance для вызова, если метод - экземплярный 962 | if (!callingMethodIsStatic) restoreInstance(callingMethodType); 963 | 964 | // Восстанавливаем аргументы вызова (для вызова unpatchable кода они были сохранены) 965 | restoreArgs(callingMethodType); 966 | 967 | // Устанавливаем флаг того, что мы вызываем unpatchable метод 968 | mv.visitLdcInsn(1); 969 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "setUnpatchableCall", "(Z)V", false); 970 | } 971 | 972 | // Сюда приходим сразу, если нет необходимости восстанавливать стек 973 | mv.visitLabel(noRestoringLabel); 974 | putFrame(currentFrame()); 975 | 976 | boolean callingMethodIsStatic = (opcode == Opcodes.INVOKESTATIC); 977 | final Type callingMethodType = Type.getType(desc); 978 | final Type[] argumentTypes = callingMethodType.getArgumentTypes(); 979 | 980 | // Offsets for each arg in temporary stacks 981 | int[] offsets = calculateUnpatchableOffsets(argumentTypes, callingMethodIsStatic); 982 | 983 | // Push instance and args to temporary storage before call 984 | saveArgsInTempStorage(argumentTypes, callingMethodIsStatic, offsets, owner); 985 | 986 | Label beforeCallLabel = new Label(); 987 | mv.visitLabel(beforeCallLabel); 988 | 989 | // Original call 990 | super.visitMethodInsn(opcode, owner, name, desc, itf); 991 | 992 | Label afterCallLabel = new Label(); 993 | mv.visitLabel(afterCallLabel); 994 | 995 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "isYielding", "()Z", false); 996 | Label noSaveContextLabel = new Label(); 997 | mv.visitJumpInsn(Opcodes.IFEQ, noSaveContextLabel); 998 | 999 | // Save execution context 1000 | { 1001 | // Save args of calling unpatchable method 1002 | saveArgs(argumentTypes, offsets, callingMethodIsStatic); 1003 | 1004 | // Clean the used space in unpatchables storage 1005 | cleanUnpatchablesFrame(argumentTypes, callingMethodIsStatic); 1006 | 1007 | // Save stack 1008 | saveStackBottom(Type.getReturnType(desc)); 1009 | 1010 | // Save locals 1011 | saveLocals(); 1012 | 1013 | // Save "this" if method is instance method 1014 | saveThis(); 1015 | 1016 | // Save the state 1017 | mv.visitLdcInsn(restorePointsProcessed); 1018 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/jcoro/Coro", "pushState", "(I)V", false); 1019 | 1020 | // And return 0 or null 1021 | returnDefault(); 1022 | } 1023 | mv.visitLabel(noSaveContextLabel); 1024 | putFrame(nextFrame()); 1025 | 1026 | cleanUnpatchablesFrame(argumentTypes, callingMethodIsStatic); 1027 | 1028 | Label noExceptionLabel = new Label(); 1029 | mv.visitJumpInsn(Opcodes.GOTO, noExceptionLabel); 1030 | 1031 | Label exceptionLabel = new Label(); 1032 | mv.visitLabel(exceptionLabel); 1033 | putFrame(nextFrame(), "Ljava/lang/Throwable;"); 1034 | 1035 | cleanUnpatchablesFrame(argumentTypes, callingMethodIsStatic); 1036 | 1037 | Label endNoExceptionsBlockLabel = new Label(); 1038 | mv.visitLabel(endNoExceptionsBlockLabel); 1039 | mv.visitInsn(Opcodes.ATHROW); 1040 | 1041 | mv.visitLabel(noExceptionLabel); 1042 | putFrame(nextFrame()); 1043 | 1044 | excludeBlock.label_2 = endNoExceptionsBlockLabel; 1045 | 1046 | // All exceptions from original call are rethrowed after cleaning unpatchables frame 1047 | mv.visitTryCatchBlock(beforeCallLabel, afterCallLabel, exceptionLabel, "Ljava/lang/Throwable;"); 1048 | 1049 | restorePointsProcessed++; 1050 | } 1051 | 1052 | @Override 1053 | public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 1054 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.METHOD_INSN; 1055 | 1056 | MethodId callingMethodId = new MethodId(owner, name, desc); 1057 | // Do nothing if this call is not restore point call 1058 | if (!analyzeResult.getRestorePoints().contains(callingMethodId)) { 1059 | mv.visitMethodInsn(opcode, owner, name, desc, itf); 1060 | insnIndex++; 1061 | return; 1062 | } 1063 | 1064 | if (analyzeResult.getUnpatchableRestorePoints() != null && 1065 | analyzeResult.getUnpatchableRestorePoints().contains(callingMethodId)) { 1066 | visitMethodInsnUnpatchable(opcode, owner, name, desc, itf); 1067 | } else { 1068 | visitMethodInsnPatchable(opcode, owner, name, desc, itf); 1069 | } 1070 | 1071 | insnIndex++; 1072 | } 1073 | 1074 | @Override 1075 | public void visitMaxs(int maxStack, int maxLocals) { 1076 | super.visitMaxs(maxStack, maxLocals); 1077 | } 1078 | 1079 | @Override 1080 | public void visitFieldInsn(int opcode, String owner, String name, String desc) { 1081 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.FIELD_INSN; 1082 | super.visitFieldInsn(opcode, owner, name, desc); 1083 | insnIndex++; 1084 | noInsnsSinceLastFrame = false; 1085 | } 1086 | 1087 | @Override 1088 | public void visitInsn(int opcode) { 1089 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.INSN; 1090 | super.visitInsn(opcode); 1091 | insnIndex++; 1092 | noInsnsSinceLastFrame = false; 1093 | } 1094 | 1095 | @Override 1096 | public void visitIntInsn(int opcode, int operand) { 1097 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.INT_INSN; 1098 | super.visitIntInsn(opcode, operand); 1099 | insnIndex++; 1100 | noInsnsSinceLastFrame = false; 1101 | } 1102 | 1103 | @Override 1104 | public void visitVarInsn(int opcode, int var) { 1105 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.VAR_INSN; 1106 | super.visitVarInsn(opcode, var); 1107 | insnIndex++; 1108 | noInsnsSinceLastFrame = false; 1109 | } 1110 | 1111 | @Override 1112 | public void visitTypeInsn(int opcode, String type) { 1113 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.TYPE_INSN; 1114 | super.visitTypeInsn(opcode, type); 1115 | insnIndex++; 1116 | noInsnsSinceLastFrame = false; 1117 | } 1118 | 1119 | /** 1120 | * Shouldn't called by ASM because it is deprecated; 1121 | * shouldn't called by our code, because we can avoid calling it :) 1122 | */ 1123 | @Override 1124 | public void visitMethodInsn(int opcode, String owner, String name, String desc) { 1125 | throw new UnsupportedOperationException(); 1126 | } 1127 | 1128 | @Override 1129 | public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { 1130 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN; 1131 | super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); 1132 | insnIndex++; 1133 | noInsnsSinceLastFrame = false; 1134 | } 1135 | 1136 | @Override 1137 | public void visitJumpInsn(int opcode, Label label) { 1138 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.JUMP_INSN; 1139 | super.visitJumpInsn(opcode, label); 1140 | insnIndex++; 1141 | noInsnsSinceLastFrame = false; 1142 | } 1143 | 1144 | @Override 1145 | public void visitLdcInsn(Object cst) { 1146 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.LDC_INSN; 1147 | super.visitLdcInsn(cst); 1148 | insnIndex++; 1149 | noInsnsSinceLastFrame = false; 1150 | } 1151 | 1152 | @Override 1153 | public void visitIincInsn(int var, int increment) { 1154 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.IINC_INSN; 1155 | super.visitIincInsn(var, increment); 1156 | insnIndex++; 1157 | noInsnsSinceLastFrame = false; 1158 | } 1159 | 1160 | @Override 1161 | public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { 1162 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.TABLESWITCH_INSN; 1163 | super.visitTableSwitchInsn(min, max, dflt, labels); 1164 | insnIndex++; 1165 | noInsnsSinceLastFrame = false; 1166 | } 1167 | 1168 | @Override 1169 | public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 1170 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.LOOKUPSWITCH_INSN; 1171 | super.visitLookupSwitchInsn(dflt, keys, labels); 1172 | insnIndex++; 1173 | noInsnsSinceLastFrame = false; 1174 | } 1175 | 1176 | @Override 1177 | public void visitMultiANewArrayInsn(String desc, int dims) { 1178 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.MULTIANEWARRAY_INSN; 1179 | super.visitMultiANewArrayInsn(desc, dims); 1180 | insnIndex++; 1181 | noInsnsSinceLastFrame = false; 1182 | } 1183 | 1184 | private boolean noInsnsSinceLastFrame = true; 1185 | 1186 | @Override 1187 | public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { 1188 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.FRAME; 1189 | 1190 | // Если эту проверку убрать, возможны случаи, когда подряд будут идти 2 фрейма, а между ними 1191 | // ни одной значащей инструкции. В этой ситуации ASM ругнётся IllegalStateException, т.к. ожидает 1192 | // в таких случаях только фрейм с типом SAME 1193 | if (!noInsnsSinceLastFrame) { 1194 | this.putFrame(currentFrame()); 1195 | } 1196 | 1197 | insnIndex++; 1198 | } 1199 | 1200 | @Override 1201 | public void visitLabel(Label label) { 1202 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.LABEL; 1203 | super.visitLabel(label); 1204 | insnIndex++; 1205 | } 1206 | 1207 | @Override 1208 | public void visitLineNumber(int line, Label start) { 1209 | assert analyzeResult.getInsns()[insnIndex].getType() == AbstractInsnNode.LINE; 1210 | super.visitLineNumber(line, start); 1211 | insnIndex++; 1212 | } 1213 | } 1214 | --------------------------------------------------------------------------------