From 9fd152943ff3dc0740ecfaf4d04fd25b3b05b7c8 Mon Sep 17 00:00:00 2001 From: Alexander Bezzubov Date: Thu, 4 Jul 2019 11:52:59 +0200 Subject: [PATCH 1/2] v2: implement encoding from JVM Signed-off-by: Alexander Bezzubov --- src/main/native/jni_utils.cc | 10 +- src/main/native/jni_utils.h | 1 + .../native/org_bblfsh_client_v2_Context.h | 20 +-- .../native/org_bblfsh_client_v2_ContextExt.h | 45 ++++++ .../org_bblfsh_client_v2_libuast_Libuast.cc | 132 ++++++++++++------ .../org/bblfsh/client/v2/BblfshClient.scala | 44 +++--- .../scala/org/bblfsh/client/v2/Context.scala | 25 ---- .../org/bblfsh/client/v2/ContextExt.scala | 41 ++++++ .../bblfsh/client/v2/libuast/Libuast.scala | 7 +- .../client/v2/BblfshClientLoadTest.scala | 34 +++-- .../client/v2/BblfshClientParseTest.scala | 2 +- 11 files changed, 237 insertions(+), 124 deletions(-) create mode 100644 src/main/native/org_bblfsh_client_v2_ContextExt.h delete mode 100644 src/main/scala/org/bblfsh/client/v2/Context.scala create mode 100644 src/main/scala/org/bblfsh/client/v2/ContextExt.scala diff --git a/src/main/native/jni_utils.cc b/src/main/native/jni_utils.cc index 602e9aa..5a3441c 100644 --- a/src/main/native/jni_utils.cc +++ b/src/main/native/jni_utils.cc @@ -20,8 +20,9 @@ JNIEnv *getJNIEnv() { return pEnv; } +// Class fully qualified names const char CLS_NODE[] = "org/bblfsh/client/v2/Node"; -const char CLS_CTX[] = "org/bblfsh/client/v2/Context"; +const char CLS_CTX[] = "org/bblfsh/client/v2/ContextExt"; const char CLS_OBJ[] = "java/lang/Object"; const char CLS_RE[] = "java/lang/RuntimeException"; const char CLS_JNODE[] = "org/bblfsh/client/v2/JNode"; @@ -34,6 +35,7 @@ const char CLS_JUINT[] = "org/bblfsh/client/v2/JUint"; const char CLS_JARR[] = "org/bblfsh/client/v2/JArray"; const char CLS_JOBJ[] = "org/bblfsh/client/v2/JObject"; +// Method signatures const char METHOD_JNODE_KEY_AT[] = "(I)Ljava/lang/String;"; const char METHOD_JNODE_VALUE_AT[] = "(I)Lorg/bblfsh/client/v2/JNode;"; const char METHOD_JOBJ_ADD[] = @@ -126,8 +128,8 @@ jfieldID getField(JNIEnv *env, jobject obj, const char *name) { return jfid; } -static jmethodID MethodID(JNIEnv *env, const char *method, - const char *signature, const char *className) { +jmethodID MethodID(JNIEnv *env, const char *method, const char *signature, + const char *className) { jclass cls = env->FindClass(className); checkJvmException(std::string("failed to find a class ").append(className)); @@ -171,7 +173,7 @@ jobject ObjectMethod(JNIEnv *env, const char *method, const char *signature, va_start(varargs, object); jobject res = env->CallObjectMethodV(*object, mId, varargs); va_end(varargs); - checkJvmException(std::string("failed get varargs for ") + checkJvmException(std::string("failed to get varargs for ") .append(className) .append(".") .append(method)); diff --git a/src/main/native/jni_utils.h b/src/main/native/jni_utils.h index d1801ae..3794490 100644 --- a/src/main/native/jni_utils.h +++ b/src/main/native/jni_utils.h @@ -44,4 +44,5 @@ jint IntMethod(JNIEnv *, const char *, const char *, const char *, jobject ObjectMethod(JNIEnv *, const char *, const char *, const char *, const jobject *, ...); +jmethodID MethodID(JNIEnv *, const char *, const char *, const char *); #endif diff --git a/src/main/native/org_bblfsh_client_v2_Context.h b/src/main/native/org_bblfsh_client_v2_Context.h index 4deed86..57bde96 100644 --- a/src/main/native/org_bblfsh_client_v2_Context.h +++ b/src/main/native/org_bblfsh_client_v2_Context.h @@ -10,15 +10,23 @@ extern "C" { /* * Class: org_bblfsh_client_v2_Context * Method: root - * Signature: ()Lorg/bblfsh/client/v2/Node; + * Signature: ()Lorg/bblfsh/client/v2/JNode; */ JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_Context_root (JNIEnv *, jobject); +/* + * Class: org_bblfsh_client_v2_Context + * Method: filter + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_bblfsh_client_v2_Context_filter + (JNIEnv *, jobject); + /* * Class: org_bblfsh_client_v2_Context * Method: encode - * Signature: (Lorg/bblfsh/client/v2/Node;)Ljava/nio/ByteBuffer; + * Signature: (Lorg/bblfsh/client/v2/JNode;)Ljava/nio/ByteBuffer; */ JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_Context_encode (JNIEnv *, jobject, jobject); @@ -31,14 +39,6 @@ JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_Context_encode JNIEXPORT void JNICALL Java_org_bblfsh_client_v2_Context_dispose (JNIEnv *, jobject); -/* - * Class: org_bblfsh_client_v2_Context - * Method: filter - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_org_bblfsh_client_v2_Context_filter - (JNIEnv *, jobject); - #ifdef __cplusplus } #endif diff --git a/src/main/native/org_bblfsh_client_v2_ContextExt.h b/src/main/native/org_bblfsh_client_v2_ContextExt.h new file mode 100644 index 0000000..9287e9b --- /dev/null +++ b/src/main/native/org_bblfsh_client_v2_ContextExt.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_bblfsh_client_v2_ContextExt */ + +#ifndef _Included_org_bblfsh_client_v2_ContextExt +#define _Included_org_bblfsh_client_v2_ContextExt +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_bblfsh_client_v2_ContextExt + * Method: root + * Signature: ()Lorg/bblfsh/client/v2/Node; + */ +JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_ContextExt_root + (JNIEnv *, jobject); + +/* + * Class: org_bblfsh_client_v2_ContextExt + * Method: filter + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_bblfsh_client_v2_ContextExt_filter + (JNIEnv *, jobject); + +/* + * Class: org_bblfsh_client_v2_ContextExt + * Method: encode + * Signature: (Lorg/bblfsh/client/v2/Node;)Ljava/nio/ByteBuffer; + */ +JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_ContextExt_encode + (JNIEnv *, jobject, jobject); + +/* + * Class: org_bblfsh_client_v2_ContextExt + * Method: dispose + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_bblfsh_client_v2_ContextExt_dispose + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc b/src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc index 5c8d182..e34b328 100644 --- a/src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc +++ b/src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc @@ -2,6 +2,7 @@ #include "jni_utils.h" #include "org_bblfsh_client_v2_Context.h" +#include "org_bblfsh_client_v2_ContextExt.h" #include "org_bblfsh_client_v2_Context__.h" #include "org_bblfsh_client_v2_Node.h" #include "org_bblfsh_client_v2_libuast_Libuast.h" @@ -72,9 +73,7 @@ class ContextExt { JNIEnv *env = getJNIEnv(); jclass cls = env->FindClass(CLS_NODE); - if (env->ExceptionOccurred() || !cls) { - return 0; - } + checkJvmException("failed to find class " + std::string(CLS_NODE)); if (!env->IsInstanceOf(obj, cls)) { const char *err = "ContextExt.toHandle() called not on Node type"; @@ -84,9 +83,7 @@ class ContextExt { auto handle = (NodeHandle)env->GetLongField(obj, getField(env, obj, "handle")); - if (env->ExceptionOccurred() || !handle) { - return 0; - } + checkJvmException("failed to get field Node.handle"); return handle; } @@ -103,11 +100,12 @@ class ContextExt { return toJ(root); } - // Encode serializes external UAST. + // Encode serializes the external UAST. // Borrows the reference. jobject Encode(jobject node, UastFormat format) { - NodeHandle h = toHandle(node); - uast::Buffer data = ctx->Encode(h, format); + // if (!assertNotContext(node)) return nullptr; + + uast::Buffer data = ctx->Encode(toHandle(node), format); return asJvmBuffer(data); } }; @@ -131,7 +129,7 @@ class Node : public uast::Node { static NodeKind kindOf(jobject obj) { JNIEnv *env = getJNIEnv(); // TODO(bzz): expose JNode.kind & replace type comparison \w a string test - if (!obj) { + if (!obj || env->IsInstanceOf(obj, env->FindClass(CLS_JNULL))) { return NODE_NULL; } else if (env->IsInstanceOf(obj, env->FindClass(CLS_JSTR))) { return NODE_STRING; @@ -189,49 +187,71 @@ class Node : public uast::Node { NodeKind Kind() { return kind; } - // TODO(#90): implement and test (all 'As*' are unused stubs for now) - std::string *AsString() { + std::string *AsString() { // new ref if (!str) { + const char methodName[] = "str"; JNIEnv *env = getJNIEnv(); - const char *utf = env->GetStringUTFChars((jstring)obj, 0); + jstring jstr = (jstring)ObjectMethod( + env, methodName, "()Ljava/lang/String;", CLS_JSTR, &obj); + + const char *utf = env->GetStringUTFChars(jstr, 0); str = new std::string(utf); - env->ReleaseStringUTFChars((jstring)obj, utf); + env->ReleaseStringUTFChars(jstr, utf); } std::string *s = new std::string(*str); return s; } int64_t AsInt() { + const char methodName[] = "num"; JNIEnv *env = getJNIEnv(); - jclass cls = env->FindClass("java/lang/Integer"); - jmethodID valueId = env->GetMethodID(cls, "longValue", "()J"); - long long value = (long long)env->CallLongMethod(obj, valueId); + jmethodID mID = MethodID(env, methodName, "()J", CLS_JINT); + + long long value = (long long)env->CallLongMethod(obj, mID); + checkJvmException(std::string("failed to call ") + .append(CLS_JINT) + .append(".") + .append(methodName) + .append(" at Node::AsInt()")); return (int64_t)(value); } uint64_t AsUint() { + const char methodName[] = "get"; JNIEnv *env = getJNIEnv(); - jclass cls = env->FindClass("java/lang/Integer"); - jmethodID valueId = env->GetMethodID(cls, "intValue", "()I"); - jlong value = env->CallIntMethod(obj, valueId); + jmethodID mID = MethodID(env, methodName, "()J", CLS_JUINT); - jmethodID mId = env->GetMethodID(cls, "toUnsignedLong", "(I)J"); - jlong v = env->CallLongMethod(obj, mId, value); - - return (uint64_t)(v); + jlong value = env->CallLongMethod(obj, mID); + checkJvmException(std::string("failed to call ") + .append(CLS_JUINT) + .append(".") + .append(methodName) + .append(" at Node::AsUint()")); + return (uint64_t)(value); } double AsFloat() { + const char methodName[] = "num"; JNIEnv *env = getJNIEnv(); - jclass cls = env->FindClass("java/lang/Double"); - jmethodID valueId = env->GetMethodID(cls, "floatValue", "()F"); - float value = (float)env->CallFloatMethod(obj, valueId); + jmethodID mID = MethodID(env, methodName, "()D", CLS_JFLT); + + double value = (double)env->CallDoubleMethod(obj, mID); + checkJvmException(std::string("failed to call ") + .append(CLS_JFLT) + .append(".") + .append(methodName) + .append(" at Node::AsFloat()")); return value; } bool AsBool() { + const char methodName[] = "value"; JNIEnv *env = getJNIEnv(); - // TODO(bzz) check failures, cache classes, read 'value' filed - jclass cls = env->FindClass("java/lang/Boolean"); - jmethodID valueId = env->GetMethodID(cls, "booleanValue", "()Z"); - bool value = (bool)env->CallBooleanMethod(obj, valueId); + jmethodID mID = MethodID(env, methodName, "()Z", CLS_JBOOL); + + bool value = (bool)env->CallBooleanMethod(obj, mID); + checkJvmException(std::string("failed to call ") + .append(CLS_JBOOL) + .append(".") + .append(methodName) + .append(" at Node::AsBool()")); return value; } size_t Size() { @@ -245,7 +265,7 @@ class Node : public uast::Node { JNIEnv *env = getJNIEnv(); jstring key = (jstring)ObjectMethod(env, "keyAt", METHOD_JNODE_KEY_AT, - CLS_JNODE, &obj); + CLS_JNODE, &obj, i); const char *k = env->GetStringUTFChars(key, 0); std::string *s = new std::string(k); @@ -258,7 +278,7 @@ class Node : public uast::Node { JNIEnv *env = getJNIEnv(); jobject val = - ObjectMethod(env, "valueAt", METHOD_JNODE_VALUE_AT, CLS_JNODE, &obj); + ObjectMethod(env, "valueAt", METHOD_JNODE_VALUE_AT, CLS_JNODE, &obj, i); return lookupOrCreate(env->NewGlobalRef(val)); // new ref } @@ -287,8 +307,7 @@ class Node : public uast::Node { jstring k = env->NewStringUTF(key.data()); - jobject res = - ObjectMethod(env, "add", METHOD_JOBJ_ADD, CLS_JOBJ, &obj, k, v); + ObjectMethod(env, "add", METHOD_JOBJ_ADD, CLS_JOBJ, &obj, k, v); checkJvmException( std::string("failed to call JObject.add() from Node::SetKeyValue(") .append(key) @@ -404,6 +423,9 @@ class Context { if (node == nullptr) return nullptr; return iface->toJ(node); } + // toNode returns a node associated with a JVM object. + // Returns a new reference. + Node *toNode(jobject obj) { return iface->lookupOrCreate(obj); } public: Context() { @@ -427,6 +449,16 @@ class Context { return toJ(root); // new ref } + // Encode serializes UAST. + // Creates a new reference. + jobject Encode(jobject node, UastFormat format) { + // if (!assertNotContext(node)) return nullptr; + + Node *n = toNode(node); + uast::Buffer data = ctx->Encode(n, format); + return asJvmBuffer(data); + } + jobject LoadFrom(jobject src) { // JNode JNIEnv *env = getJNIEnv(); @@ -474,13 +506,13 @@ JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_libuast_Libuast_decode( jCtxExt = nullptr; delete (ctx); delete (p); - checkJvmException("failed to instantiate Context class"); + checkJvmException("failed to instantiate ContextExt class"); } return jCtxExt; } -// TODO(#86): implement +// TODO(#83): implement JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_libuast_Libuast_filter( JNIEnv *, jobject, jobject, jstring) { return nullptr; @@ -490,21 +522,31 @@ JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_libuast_Libuast_filter( // v2.Context() // ========================================== +JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_Context_encode( + JNIEnv *env, jobject self, jobject node) { + UastFormat fmt = UAST_BINARY; // TODO(bzz): make it argument + + Context *p = getHandle(env, self, nativeContext); + return p->Encode(node, fmt); +} + JNIEXPORT jlong JNICALL Java_org_bblfsh_client_v2_Context_00024_create(JNIEnv *env, jobject self) { auto c = new Context(); - uast::Context *ctx; // TODO(#90): init from c on encode() impl - auto p = new ContextExt(ctx); - return (long)p; + return (long)c; } -JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_Context_root(JNIEnv *env, - jobject self) { +// ========================================== +// v2.ContextExt() +// ========================================== + +JNIEXPORT jobject JNICALL +Java_org_bblfsh_client_v2_ContextExt_root(JNIEnv *env, jobject self) { ContextExt *p = getHandle(env, self, nativeContext); return p->RootNode(); } -JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_Context_encode( +JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_ContextExt_encode( JNIEnv *env, jobject self, jobject node) { UastFormat fmt = UAST_BINARY; // TODO(bzz): make it argument & enum @@ -512,8 +554,8 @@ JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_Context_encode( return p->Encode(node, fmt); } -JNIEXPORT void JNICALL Java_org_bblfsh_client_v2_Context_dispose(JNIEnv *env, - jobject self) { +JNIEXPORT void JNICALL +Java_org_bblfsh_client_v2_ContextExt_dispose(JNIEnv *env, jobject self) { ContextExt *p = getHandle(env, self, nativeContext); setHandle(env, self, 0, nativeContext); delete p; diff --git a/src/main/scala/org/bblfsh/client/v2/BblfshClient.scala b/src/main/scala/org/bblfsh/client/v2/BblfshClient.scala index 369d2b8..23e8c0f 100644 --- a/src/main/scala/org/bblfsh/client/v2/BblfshClient.scala +++ b/src/main/scala/org/bblfsh/client/v2/BblfshClient.scala @@ -3,10 +3,8 @@ package org.bblfsh.client.v2 import java.nio.ByteBuffer import com.google.protobuf.ByteString -import com.google.protobuf.duration.Duration +import gopkg.in.bblfsh.sdk.v2.protocol.driver._ import io.grpc.ManagedChannelBuilder -import gopkg.in.bblfsh.sdk.v2.protocol.driver.{ParseRequest, ParseResponse, DriverGrpc, SupportedLanguagesRequest, SupportedLanguagesResponse, VersionRequest, VersionResponse, DriverHostGrpc} - import org.bblfsh.client.v2.libuast.Libuast @@ -26,9 +24,9 @@ class BblfshClient(host: String, port: Int, maxMsgSize: Int) { * Parses file with a given name and content using * the provided timeout. * - * @param name file name + * @param name file name * @param content file content - * @param lang (optional) language to parse, default auto-detect \w enry + * @param lang (optional) language to parse, default auto-detect \w enry * @param timeout (disabled) bblfsh request timeout, seconds * Right now this does not have any effect in v2. * @return UAST in parse response. @@ -54,9 +52,9 @@ class BblfshClient(host: String, port: Int, maxMsgSize: Int) { * A default timeout of 5 seconds will be applied, same as was done by the server * before https://github.com/bblfsh/bblfshd/blob/83166ea0087bfe20c24fc471309f70f422383198/daemon/pool.go#L191 * - * @param name file name + * @param name file name * @param content file content - * @param lang (optional) language to parse, default auto-detect \w enry + * @param lang (optional) language to parse, default auto-detect \w enry * @return UAST in parse response. */ def parse( @@ -71,10 +69,10 @@ class BblfshClient(host: String, port: Int, maxMsgSize: Int) { * * Since v1.11, this API exposes the timeout. * - * @param name file name + * @param name file name * @param content file content * @param timeout bblfsh request timeout, seconds - * @param lang (optional) language to parse, default auto-detect \w enry + * @param lang (optional) language to parse, default auto-detect \w enry * @return */ def parseWithTimeout( @@ -102,9 +100,10 @@ class BblfshClient(host: String, port: Int, maxMsgSize: Int) { object BblfshClient { val DEFAULT_MAX_MSG_SIZE = 100 * 1024 * 1024 // bytes - val PreOrder = 0 - val PostOrder = 1 - val LevelOrder = 2 + // TODO(bzz): expose new 'children' order and use enum/case class + val PreOrder = 0 + val PostOrder = 1 + val LevelOrder = 2 val PositionOrder = 3 private val libuast = new Libuast @@ -114,18 +113,17 @@ object BblfshClient { maxMsgSize: Int = DEFAULT_MAX_MSG_SIZE ): BblfshClient = new BblfshClient(host, port, maxMsgSize) - def filter(node: Node, query: String): List[Node] = Libuast.synchronized { libuast.filter(node, query) } /** - * Decodes bytes from wired format of bblfsh protocol.v2. - * Requires a buffer in Direct mode. - * - * Since v2. - */ - def decode(buf: ByteBuffer): Context = Libuast.synchronized { + * Decodes bytes from wired format of bblfsh protocol.v2. + * Requires a buffer in Direct mode. + * + * Since v2. + */ + def decode(buf: ByteBuffer): ContextExt = Libuast.synchronized { if (!buf.isDirect()) { throw new RuntimeException("Only directly-allocated buffer decoding is supported.") } @@ -135,10 +133,10 @@ object BblfshClient { // Enables API: resp.uast.decode() implicit class UastMethods(val buf: ByteString) { /** - * Decodes bytes from wired format of bblfsh protocol.v2. - * Copies a buffer in Direct mode. - */ - def decode(): Context = { + * Decodes bytes from wired format of bblfsh protocol.v2. + * Copies a buffer in Direct mode. + */ + def decode(): ContextExt = { val bufDirectCopy = ByteBuffer.allocateDirect(buf.size) buf.copyTo(bufDirectCopy) BblfshClient.decode(bufDirectCopy) diff --git a/src/main/scala/org/bblfsh/client/v2/Context.scala b/src/main/scala/org/bblfsh/client/v2/Context.scala deleted file mode 100644 index fe84523..0000000 --- a/src/main/scala/org/bblfsh/client/v2/Context.scala +++ /dev/null @@ -1,25 +0,0 @@ -package org.bblfsh.client.v2 - -import java.nio.ByteBuffer - -case class Context(nativeContext: Long) { - def this() = this(Context.create()) - - @native def root(): Node - @native def encode(n: Node): ByteBuffer - @native def dispose() - - @native def filter() - - // TODO(bzz): add loading of the root node, after clarifying when it's needed - // https://github.com/bblfsh/client-python/blob/master/bblfsh/pyuast.cc#L364 - // @native def load(): ? - - override def finalize(): Unit = { - this.dispose() - } -} - -object Context { - @native def create(): Long -} diff --git a/src/main/scala/org/bblfsh/client/v2/ContextExt.scala b/src/main/scala/org/bblfsh/client/v2/ContextExt.scala new file mode 100644 index 0000000..96aca80 --- /dev/null +++ b/src/main/scala/org/bblfsh/client/v2/ContextExt.scala @@ -0,0 +1,41 @@ +package org.bblfsh.client.v2 + +import java.nio.ByteBuffer + +/** + * pyuast.ContextExt + * + * Represents Go-side results of Libuast.decode() + */ +case class ContextExt(nativeContext: Long) { + @native def root(): Node + // @native def load() // TODO(bzz): implement after clarifying when it's needed + @native def filter() + @native def encode(n: Node): ByteBuffer + + @native def dispose() + override def finalize(): Unit = { + this.dispose() + } +} + + +/** + * pyuast.Context + * + * Represents JVM-side constructed tree. + */ +case class Context(nativeContext: Long) { + @native def root(): JNode + @native def filter() + @native def encode(n: JNode): ByteBuffer + + @native def dispose() + override def finalize(): Unit = { + this.dispose() + } +} +object Context { + @native def create(): Long + def apply(): Context = new Context(create()) +} diff --git a/src/main/scala/org/bblfsh/client/v2/libuast/Libuast.scala b/src/main/scala/org/bblfsh/client/v2/libuast/Libuast.scala index a1ec257..40e106a 100644 --- a/src/main/scala/org/bblfsh/client/v2/libuast/Libuast.scala +++ b/src/main/scala/org/bblfsh/client/v2/libuast/Libuast.scala @@ -1,14 +1,13 @@ package org.bblfsh.client.v2.libuast -import org.bblfsh.client.v2.Node -import org.bblfsh.client.v2.Context +import org.bblfsh.client.v2.{ContextExt, JNode, Node} import scala.collection.Iterator import java.io.File import java.nio.file.Paths import java.nio.ByteBuffer -import org.apache.commons.io.{IOUtils, FileUtils} +import org.apache.commons.io.{FileUtils, IOUtils} object Libuast { final var loaded = false @@ -86,6 +85,6 @@ class Libuast { // new Libuast.UastIterator(node, treeOrder) // } - @native def decode(buf: ByteBuffer): Context + @native def decode(buf: ByteBuffer): ContextExt @native def filter(node: Node, query: String): List[Node] } diff --git a/src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala b/src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala index e143fa4..26b1f41 100644 --- a/src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala +++ b/src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala @@ -1,5 +1,7 @@ package org.bblfsh.client.v2 +import java.nio.ByteBuffer + class BblfshClientLoadTest extends BblfshClientBaseTest { @@ -34,22 +36,23 @@ class BblfshClientLoadTest extends BblfshClientBaseTest { root.children.foreach(println) } - // TODO(#90) a stub for testing JNode encoding impl - /*"Loading Go -> JVM for a simple encoded tree" should "bring JNode tree to memory" in { - val rootTree: JNode = JArray(Buffer( - JObject(Buffer( + "Loading Go -> JVM for a simple encoded tree" should "bring JNode tree to memory" in { + val rootTree: JNode = JArray( + JObject( "k1" -> JString("v1") - )), + ), JString("test") - )) + ) + + rootTree.size should be (2) val ctx = Context() + val bb: ByteBuffer = ctx.encode(rootTree) - ctx.encode(rootTree) - }*/ + bb should not be (null) + } - // TODO(#90) a stub for more extensive testing of JNode encoding impl - /*"Decoding, loading & encoding to different context" should "produce the same results" in { + "Decoding, loading & encoding to different context" should "produce the same results" in { val uast = resp.uast.decode() val rootNode: Node = uast.root() @@ -59,7 +62,14 @@ class BblfshClientLoadTest extends BblfshClientBaseTest { val ctx = Context() val data = ctx.encode(root) - data shouldBe equal resp.uast - }*/ + val uast2 = BblfshClient.decode(data) + val rootNode2: Node = uast2.root() + println(s"Loading $rootNode2") + val root2 = rootNode2.load() + root2 should equal (root) + + data should equal (resp.uast.asReadOnlyByteBuffer()) +// data.compareTo(resp.uast.asReadOnlyByteBuffer()) shouldBe 0 + } } diff --git a/src/test/scala/org/bblfsh/client/v2/BblfshClientParseTest.scala b/src/test/scala/org/bblfsh/client/v2/BblfshClientParseTest.scala index bb0ad6e..694c46c 100644 --- a/src/test/scala/org/bblfsh/client/v2/BblfshClientParseTest.scala +++ b/src/test/scala/org/bblfsh/client/v2/BblfshClientParseTest.scala @@ -35,7 +35,7 @@ class BblfshClientParseTest extends BblfshClientBaseTest { } "Encoding back the RootNode of decoded UAST" should "produce same bytes" in { - val uastCtx: Context = resp.uast.decode() + val uastCtx: ContextExt = resp.uast.decode() val rootNode: Node = uastCtx.root() println(s"Root node: $rootNode") From 54ea17bf43cb14e10048c3d2dff1260d2cd9f7ce Mon Sep 17 00:00:00 2001 From: Alexander Bezzubov Date: Thu, 4 Jul 2019 21:36:45 +0200 Subject: [PATCH 2/2] address review feedback - add new runtime sanity-checks - better doc - remoced '== null' style comparison Signed-off-by: Alexander Bezzubov --- .../org_bblfsh_client_v2_libuast_Libuast.cc | 34 ++++++++++++++++--- .../org/bblfsh/client/v2/ContextExt.scala | 8 ++--- .../client/v2/BblfshClientLoadTest.scala | 9 ++--- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc b/src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc index e34b328..46f820c 100644 --- a/src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc +++ b/src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc @@ -50,6 +50,28 @@ jobject asJvmBuffer(uast::Buffer buf) { return env->NewDirectByteBuffer(buf.ptr, buf.size); } +// Checks if a given object is of Node/JNode class +bool isContext(jobject obj, JNIEnv *env) { + if (!obj) return false; + + jclass ctxCls = env->FindClass(CLS_CTX); + checkJvmException("failed to find class " + std::string(CLS_CTX)); + + return env->IsInstanceOf(obj, ctxCls); +} + +bool assertNotContext(jobject obj) { + JNIEnv *env = getJNIEnv(); + if (isContext(obj, env)) { + auto reCls = env->FindClass(CLS_RE); + checkJvmException("failed to find class " + std::string(CLS_RE)); + + env->ThrowNew(reCls, "cannot use UAST Context as a Node"); + return false; + } + return true; +} + // ========================================== // External UAST Context (managed by libuast) // ========================================== @@ -76,7 +98,9 @@ class ContextExt { checkJvmException("failed to find class " + std::string(CLS_NODE)); if (!env->IsInstanceOf(obj, cls)) { - const char *err = "ContextExt.toHandle() called not on Node type"; + auto err = std::string("ContextExt.toHandle() called not on") + .append(CLS_NODE) + .append(" type"); ctx->SetError(err); return 0; } @@ -103,7 +127,7 @@ class ContextExt { // Encode serializes the external UAST. // Borrows the reference. jobject Encode(jobject node, UastFormat format) { - // if (!assertNotContext(node)) return nullptr; + if (!assertNotContext(node)) return nullptr; uast::Buffer data = ctx->Encode(toHandle(node), format); return asJvmBuffer(data); @@ -358,7 +382,7 @@ class Interface : public uast::NodeCreator { // toJ returns a JVM object associated with a node. // Returns a new reference. jobject toJ(Node *node) { - if (node == nullptr) return nullptr; + if (!node) return nullptr; jobject obj = getJNIEnv()->NewGlobalRef(node->obj); return obj; } @@ -420,7 +444,7 @@ class Context { // toJ returns a JVM object associated with a node. // Returns a new reference. jobject toJ(Node *node) { - if (node == nullptr) return nullptr; + if (!node) return nullptr; return iface->toJ(node); } // toNode returns a node associated with a JVM object. @@ -452,7 +476,7 @@ class Context { // Encode serializes UAST. // Creates a new reference. jobject Encode(jobject node, UastFormat format) { - // if (!assertNotContext(node)) return nullptr; + if (!assertNotContext(node)) return nullptr; Node *n = toNode(node); uast::Buffer data = ctx->Encode(n, format); diff --git a/src/main/scala/org/bblfsh/client/v2/ContextExt.scala b/src/main/scala/org/bblfsh/client/v2/ContextExt.scala index 96aca80..d3185f2 100644 --- a/src/main/scala/org/bblfsh/client/v2/ContextExt.scala +++ b/src/main/scala/org/bblfsh/client/v2/ContextExt.scala @@ -3,9 +3,9 @@ package org.bblfsh.client.v2 import java.nio.ByteBuffer /** - * pyuast.ContextExt - * * Represents Go-side results of Libuast.decode() + * + * This is equivalent of pyuast.ContextExt API */ case class ContextExt(nativeContext: Long) { @native def root(): Node @@ -21,9 +21,9 @@ case class ContextExt(nativeContext: Long) { /** - * pyuast.Context + * Represents JVM-side constructed tree * - * Represents JVM-side constructed tree. + * This is equivalent of pyuast.Context API */ case class Context(nativeContext: Long) { @native def root(): JNode diff --git a/src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala b/src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala index 26b1f41..c91eb4d 100644 --- a/src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala +++ b/src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala @@ -53,23 +53,24 @@ class BblfshClientLoadTest extends BblfshClientBaseTest { } "Decoding, loading & encoding to different context" should "produce the same results" in { + // decode -> load -> encode, and compare bytes val uast = resp.uast.decode() val rootNode: Node = uast.root() println(s"Loading $rootNode") val root = rootNode.load() - val ctx = Context() val data = ctx.encode(root) + data should equal (resp.uast.asReadOnlyByteBuffer()) + + // decode -> load -> encoded -> decode -> load, and compare trees val uast2 = BblfshClient.decode(data) val rootNode2: Node = uast2.root() println(s"Loading $rootNode2") val root2 = rootNode2.load() - root2 should equal (root) - data should equal (resp.uast.asReadOnlyByteBuffer()) -// data.compareTo(resp.uast.asReadOnlyByteBuffer()) shouldBe 0 + root2 should equal (root) } }