Initial commit 🎉

fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2
Co-authored-by: Sebastian McKenzie <sebmck@fb.com>
Co-authored-by: John Knox <jknox@fb.com>
Co-authored-by: Emil Sjölander <emilsj@fb.com>
Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
Daniel Büchele
2018-04-13 08:38:06 -07:00
committed by Daniel Buchele
commit fbbf8cf16b
659 changed files with 87130 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.easywsclient">
</manifest>

View File

@@ -0,0 +1,13 @@
cmake_minimum_required (VERSION 3.6.0)
set(PACKAGE_NAME "easywsclient")
include_directories(./)
set(SOURCES easywsclient.cpp)
add_compile_options(
-std=c++11
-stdlib=libstdc++
-Wall
)
add_library(${PACKAGE_NAME} SHARED ${SOURCES})
install(TARGETS ${PACKAGE_NAME} DESTINATION ./build/)
target_link_libraries(${PACKAGE_NAME})

View File

@@ -0,0 +1,34 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.compileSdkVersion
buildToolsVersion rootProject.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
externalNativeBuild {
cmake {
arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_shared'
}
}
}
externalNativeBuild {
cmake {
path './CMakeLists.txt'
}
}
sourceSets {
main {
manifest.srcFile './AndroidManifest.xml'
}
}
}

View File

@@ -0,0 +1,529 @@
#ifdef _WIN32
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS // _CRT_SECURE_NO_WARNINGS for sscanf errors in MSVC2013 Express
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <fcntl.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment( lib, "ws2_32" )
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <io.h>
#ifndef _SSIZE_T_DEFINED
typedef int ssize_t;
#define _SSIZE_T_DEFINED
#endif
#ifndef _SOCKET_T_DEFINED
typedef SOCKET socket_t;
#define _SOCKET_T_DEFINED
#endif
#ifndef snprintf
#define snprintf _snprintf_s
#endif
#if _MSC_VER >=1600
// vs2010 or later
#include <stdint.h>
#else
typedef __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#endif
#define socketerrno WSAGetLastError()
#define SOCKET_EAGAIN_EINPROGRESS WSAEINPROGRESS
#define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK
#else
#include <fcntl.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#ifndef _SOCKET_T_DEFINED
typedef int socket_t;
#define _SOCKET_T_DEFINED
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET (-1)
#endif
#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif
#define closesocket(s) ::close(s)
#include <errno.h>
#define socketerrno errno
#define SOCKET_EAGAIN_EINPROGRESS EAGAIN
#define SOCKET_EWOULDBLOCK EWOULDBLOCK
#endif
#include <vector>
#include <string>
#include "easywsclient.hpp"
using easywsclient::Callback_Imp;
using easywsclient::BytesCallback_Imp;
namespace { // private module-only namespace
socket_t hostname_connect(const std::string& hostname, int port) {
struct addrinfo hints;
struct addrinfo *result;
struct addrinfo *p;
int ret;
socket_t sockfd = INVALID_SOCKET;
char sport[16];
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
snprintf(sport, 16, "%d", port);
if ((ret = getaddrinfo(hostname.c_str(), sport, &hints, &result)) != 0)
{
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
return 1;
}
for(p = result; p != NULL; p = p->ai_next)
{
sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (sockfd == INVALID_SOCKET) { continue; }
if (connect(sockfd, p->ai_addr, p->ai_addrlen) != SOCKET_ERROR) {
break;
}
closesocket(sockfd);
sockfd = INVALID_SOCKET;
}
freeaddrinfo(result);
return sockfd;
}
class _DummyWebSocket : public easywsclient::WebSocket
{
public:
void poll(int timeout) { }
void send(const std::string& message) { }
void sendBinary(const std::string& message) { }
void sendBinary(const std::vector<uint8_t>& message) { }
void sendPing() { }
void close() { }
readyStateValues getReadyState() const { return CLOSED; }
void _dispatch(Callback_Imp & callable) { }
void _dispatchBinary(BytesCallback_Imp& callable) { }
};
class _RealWebSocket : public easywsclient::WebSocket
{
public:
// http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-------+-+-------------+-------------------------------+
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
// |I|S|S|S| (4) |A| (7) | (16/64) |
// |N|V|V|V| |S| | (if payload len==126/127) |
// | |1|2|3| |K| | |
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
// | Extended payload length continued, if payload len == 127 |
// + - - - - - - - - - - - - - - - +-------------------------------+
// | |Masking-key, if MASK set to 1 |
// +-------------------------------+-------------------------------+
// | Masking-key (continued) | Payload Data |
// +-------------------------------- - - - - - - - - - - - - - - - +
// : Payload Data continued ... :
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
// | Payload Data continued ... |
// +---------------------------------------------------------------+
struct wsheader_type {
unsigned header_size;
bool fin;
bool mask;
enum opcode_type {
CONTINUATION = 0x0,
TEXT_FRAME = 0x1,
BINARY_FRAME = 0x2,
CLOSE = 8,
PING = 9,
PONG = 0xa,
} opcode;
int N0;
uint64_t N;
uint8_t masking_key[4];
};
std::vector<uint8_t> rxbuf;
std::vector<uint8_t> txbuf;
std::vector<uint8_t> receivedData;
socket_t sockfd;
readyStateValues readyState;
bool useMask;
_RealWebSocket(socket_t sockfd, bool useMask) : sockfd(sockfd), readyState(OPEN), useMask(useMask) {
}
readyStateValues getReadyState() const {
return readyState;
}
void poll(int timeout) { // timeout in milliseconds
if (readyState == CLOSED) {
if (timeout > 0) {
timeval tv = { timeout/1000, (timeout%1000) * 1000 };
select(0, NULL, NULL, NULL, &tv);
}
return;
}
if (timeout != 0) {
fd_set rfds;
fd_set wfds;
timeval tv = { timeout/1000, (timeout%1000) * 1000 };
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(sockfd, &rfds);
if (txbuf.size()) { FD_SET(sockfd, &wfds); }
select(sockfd + 1, &rfds, &wfds, 0, timeout > 0 ? &tv : 0);
}
while (true) {
// FD_ISSET(0, &rfds) will be true
int N = rxbuf.size();
ssize_t ret;
rxbuf.resize(N + 1500);
ret = recv(sockfd, (char*)&rxbuf[0] + N, 1500, 0);
if (false) { }
else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
rxbuf.resize(N);
break;
}
else if (ret <= 0) {
rxbuf.resize(N);
closesocket(sockfd);
readyState = CLOSED;
fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
break;
}
else {
rxbuf.resize(N + ret);
}
}
while (txbuf.size()) {
int ret = ::send(sockfd, (char*)&txbuf[0], txbuf.size(), 0);
if (false) { } // ??
else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
break;
}
else if (ret <= 0) {
closesocket(sockfd);
readyState = CLOSED;
fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
break;
}
else {
txbuf.erase(txbuf.begin(), txbuf.begin() + ret);
}
}
if (!txbuf.size() && readyState == CLOSING) {
closesocket(sockfd);
readyState = CLOSED;
}
}
// Callable must have signature: void(const std::string & message).
// Should work with C functions, C++ functors, and C++11 std::function and
// lambda:
//template<class Callable>
//void dispatch(Callable callable)
virtual void _dispatch(Callback_Imp & callable) {
struct CallbackAdapter : public BytesCallback_Imp
// Adapt void(const std::string<uint8_t>&) to void(const std::string&)
{
Callback_Imp& callable;
CallbackAdapter(Callback_Imp& callable) : callable(callable) { }
void operator()(const std::vector<uint8_t>& message) {
std::string stringMessage(message.begin(), message.end());
callable(stringMessage);
}
};
CallbackAdapter bytesCallback(callable);
_dispatchBinary(bytesCallback);
}
virtual void _dispatchBinary(BytesCallback_Imp & callable) {
// TODO: consider acquiring a lock on rxbuf...
while (true) {
wsheader_type ws;
if (rxbuf.size() < 2) { return; /* Need at least 2 */ }
const uint8_t * data = (uint8_t *) &rxbuf[0]; // peek, but don't consume
ws.fin = (data[0] & 0x80) == 0x80;
ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f);
ws.mask = (data[1] & 0x80) == 0x80;
ws.N0 = (data[1] & 0x7f);
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
if (rxbuf.size() < ws.header_size) { return; /* Need: ws.header_size - rxbuf.size() */ }
int i = 0;
if (ws.N0 < 126) {
ws.N = ws.N0;
i = 2;
}
else if (ws.N0 == 126) {
ws.N = 0;
ws.N |= ((uint64_t) data[2]) << 8;
ws.N |= ((uint64_t) data[3]) << 0;
i = 4;
}
else if (ws.N0 == 127) {
ws.N = 0;
ws.N |= ((uint64_t) data[2]) << 56;
ws.N |= ((uint64_t) data[3]) << 48;
ws.N |= ((uint64_t) data[4]) << 40;
ws.N |= ((uint64_t) data[5]) << 32;
ws.N |= ((uint64_t) data[6]) << 24;
ws.N |= ((uint64_t) data[7]) << 16;
ws.N |= ((uint64_t) data[8]) << 8;
ws.N |= ((uint64_t) data[9]) << 0;
i = 10;
}
if (ws.mask) {
ws.masking_key[0] = ((uint8_t) data[i+0]) << 0;
ws.masking_key[1] = ((uint8_t) data[i+1]) << 0;
ws.masking_key[2] = ((uint8_t) data[i+2]) << 0;
ws.masking_key[3] = ((uint8_t) data[i+3]) << 0;
}
else {
ws.masking_key[0] = 0;
ws.masking_key[1] = 0;
ws.masking_key[2] = 0;
ws.masking_key[3] = 0;
}
if (rxbuf.size() < ws.header_size+ws.N) { return; /* Need: ws.header_size+ws.N - rxbuf.size() */ }
// We got a whole message, now do something with it:
if (false) { }
else if (
ws.opcode == wsheader_type::TEXT_FRAME
|| ws.opcode == wsheader_type::BINARY_FRAME
|| ws.opcode == wsheader_type::CONTINUATION
) {
if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } }
receivedData.insert(receivedData.end(), rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed
if (ws.fin) {
callable((const std::vector<uint8_t>) receivedData);
receivedData.erase(receivedData.begin(), receivedData.end());
std::vector<uint8_t> ().swap(receivedData);// free memory
}
}
else if (ws.opcode == wsheader_type::PING) {
if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } }
std::string data(rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);
sendData(wsheader_type::PONG, data.size(), data.begin(), data.end());
}
else if (ws.opcode == wsheader_type::PONG) { }
else if (ws.opcode == wsheader_type::CLOSE) { close(); }
else { fprintf(stderr, "ERROR: Got unexpected WebSocket message.\n"); close(); }
rxbuf.erase(rxbuf.begin(), rxbuf.begin() + ws.header_size+(size_t)ws.N);
}
}
void sendPing() {
std::string empty;
sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end());
}
void send(const std::string& message) {
sendData(wsheader_type::TEXT_FRAME, message.size(), message.begin(), message.end());
}
void sendBinary(const std::string& message) {
sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end());
}
void sendBinary(const std::vector<uint8_t>& message) {
sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end());
}
template<class Iterator>
void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end) {
// TODO:
// Masking key should (must) be derived from a high quality random
// number generator, to mitigate attacks on non-WebSocket friendly
// middleware:
const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 };
// TODO: consider acquiring a lock on txbuf...
if (readyState == CLOSING || readyState == CLOSED) { return; }
std::vector<uint8_t> header;
header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0), 0);
header[0] = 0x80 | type;
if (false) { }
else if (message_size < 126) {
header[1] = (message_size & 0xff) | (useMask ? 0x80 : 0);
if (useMask) {
header[2] = masking_key[0];
header[3] = masking_key[1];
header[4] = masking_key[2];
header[5] = masking_key[3];
}
}
else if (message_size < 65536) {
header[1] = 126 | (useMask ? 0x80 : 0);
header[2] = (message_size >> 8) & 0xff;
header[3] = (message_size >> 0) & 0xff;
if (useMask) {
header[4] = masking_key[0];
header[5] = masking_key[1];
header[6] = masking_key[2];
header[7] = masking_key[3];
}
}
else { // TODO: run coverage testing here
header[1] = 127 | (useMask ? 0x80 : 0);
header[2] = (message_size >> 56) & 0xff;
header[3] = (message_size >> 48) & 0xff;
header[4] = (message_size >> 40) & 0xff;
header[5] = (message_size >> 32) & 0xff;
header[6] = (message_size >> 24) & 0xff;
header[7] = (message_size >> 16) & 0xff;
header[8] = (message_size >> 8) & 0xff;
header[9] = (message_size >> 0) & 0xff;
if (useMask) {
header[10] = masking_key[0];
header[11] = masking_key[1];
header[12] = masking_key[2];
header[13] = masking_key[3];
}
}
// N.B. - txbuf will keep growing until it can be transmitted over the socket:
txbuf.insert(txbuf.end(), header.begin(), header.end());
txbuf.insert(txbuf.end(), message_begin, message_end);
if (useMask) {
size_t message_offset = txbuf.size() - message_size;
for (size_t i = 0; i != message_size; ++i) {
txbuf[message_offset + i] ^= masking_key[i&0x3];
}
}
}
void close() {
if(readyState == CLOSING || readyState == CLOSED) { return; }
readyState = CLOSING;
uint8_t closeFrame[6] = {0x88, 0x80, 0x00, 0x00, 0x00, 0x00}; // last 4 bytes are a masking key
std::vector<uint8_t> header(closeFrame, closeFrame+6);
txbuf.insert(txbuf.end(), header.begin(), header.end());
}
};
easywsclient::WebSocket::pointer from_url(const std::string& url, bool useMask, const std::string& origin) {
char host[128];
int port;
char path[300];
if (url.size() >= 300) {
fprintf(stderr, "ERROR: url size limit exceeded: %s\n", url.c_str());
return NULL;
}
if (origin.size() >= 200) {
fprintf(stderr, "ERROR: origin size limit exceeded: %s\n", origin.c_str());
return NULL;
}
if (false) { }
else if (sscanf(url.c_str(), "ws://%[^:/]:%d/%s", host, &port, path) == 3) {
}
else if (sscanf(url.c_str(), "ws://%[^:/]/%s", host, path) == 2) {
port = 80;
}
else if (sscanf(url.c_str(), "ws://%[^:/]:%d", host, &port) == 2) {
path[0] = '\0';
}
else if (sscanf(url.c_str(), "ws://%[^:/]", host) == 1) {
port = 80;
path[0] = '\0';
}
else {
fprintf(stderr, "ERROR: Could not parse WebSocket url: %s\n", url.c_str());
return NULL;
}
//fprintf(stderr, "easywsclient: connecting: host=%s port=%d path=/%s\n", host, port, path);
socket_t sockfd = hostname_connect(host, port);
if (sockfd == INVALID_SOCKET) {
fprintf(stderr, "Unable to connect to %s:%d\n", host, port);
return NULL;
}
{
// XXX: this should be done non-blocking,
char line[256];
int status;
int i;
snprintf(line, 256, "GET /%s HTTP/1.1\r\n", path); ::send(sockfd, line, strlen(line), 0);
if (port == 80) {
snprintf(line, 256, "Host: %s\r\n", host); ::send(sockfd, line, strlen(line), 0);
}
else {
snprintf(line, 256, "Host: %s:%d\r\n", host, port); ::send(sockfd, line, strlen(line), 0);
}
snprintf(line, 256, "Upgrade: websocket\r\n"); ::send(sockfd, line, strlen(line), 0);
snprintf(line, 256, "Connection: Upgrade\r\n"); ::send(sockfd, line, strlen(line), 0);
if (!origin.empty()) {
snprintf(line, 256, "Origin: %s\r\n", origin.c_str()); ::send(sockfd, line, strlen(line), 0);
}
snprintf(line, 256, "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"); ::send(sockfd, line, strlen(line), 0);
snprintf(line, 256, "Sec-WebSocket-Version: 13\r\n"); ::send(sockfd, line, strlen(line), 0);
snprintf(line, 256, "\r\n"); ::send(sockfd, line, strlen(line), 0);
for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } }
line[i] = 0;
if (i == 255) { fprintf(stderr, "ERROR: Got invalid status line connecting to: %s\n", url.c_str()); return NULL; }
if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { fprintf(stderr, "ERROR: Got bad status connecting to %s: %s", url.c_str(), line); return NULL; }
// TODO: verify response headers,
while (true) {
for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } }
if (line[0] == '\r' && line[1] == '\n') { break; }
}
}
int flag = 1;
setsockopt(sockfd, 6, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm
#ifdef _WIN32
u_long on = 1;
ioctlsocket(sockfd, FIONBIO, &on);
#else
fcntl(sockfd, F_SETFL, O_NONBLOCK);
#endif
//fprintf(stderr, "Connected to: %s\n", url.c_str());
return easywsclient::WebSocket::pointer(new _RealWebSocket(sockfd, useMask));
}
} // end of module-only namespace
namespace easywsclient {
WebSocket::pointer WebSocket::create_dummy() {
static pointer dummy = pointer(new _DummyWebSocket);
return dummy;
}
WebSocket::pointer WebSocket::from_url(const std::string& url, const std::string& origin) {
return ::from_url(url, true, origin);
}
WebSocket::pointer WebSocket::from_url_no_mask(const std::string& url, const std::string& origin) {
return ::from_url(url, false, origin);
}
} // namespace easywsclient

View File

@@ -0,0 +1,72 @@
#ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD
#define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD
// This code comes from:
// https://github.com/dhbaird/easywsclient
//
// To get the latest version:
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp
#include <string>
#include <vector>
namespace easywsclient {
struct Callback_Imp { virtual void operator()(const std::string& message) = 0; };
struct BytesCallback_Imp { virtual void operator()(const std::vector<uint8_t>& message) = 0; };
class WebSocket {
public:
typedef WebSocket * pointer;
typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues;
// Factories:
static pointer create_dummy();
static pointer from_url(const std::string& url, const std::string& origin = std::string());
static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string());
// Interfaces:
virtual ~WebSocket() { }
virtual void poll(int timeout = 0) = 0; // timeout in milliseconds
virtual void send(const std::string& message) = 0;
virtual void sendBinary(const std::string& message) = 0;
virtual void sendBinary(const std::vector<uint8_t>& message) = 0;
virtual void sendPing() = 0;
virtual void close() = 0;
virtual readyStateValues getReadyState() const = 0;
template<class Callable>
void dispatch(Callable callable)
// For callbacks that accept a string argument.
{ // N.B. this is compatible with both C++11 lambdas, functors and C function pointers
struct _Callback : public Callback_Imp {
Callable& callable;
_Callback(Callable& callable) : callable(callable) { }
void operator()(const std::string& message) { callable(message); }
};
_Callback callback(callable);
_dispatch(callback);
}
template<class Callable>
void dispatchBinary(Callable callable)
// For callbacks that accept a std::vector<uint8_t> argument.
{ // N.B. this is compatible with both C++11 lambdas, functors and C function pointers
struct _Callback : public BytesCallback_Imp {
Callable& callable;
_Callback(Callable& callable) : callable(callable) { }
void operator()(const std::vector<uint8_t>& message) { callable(message); }
};
_Callback callback(callable);
_dispatchBinary(callback);
}
protected:
virtual void _dispatch(Callback_Imp& callable) = 0;
virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0;
};
} // namespace easywsclient
#endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */

33
libs/fbjni/build.gradle Normal file
View File

@@ -0,0 +1,33 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.compileSdkVersion
buildToolsVersion rootProject.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
externalNativeBuild {
cmake {
arguments '-DANDROID_TOOLCHAIN=clang'
}
}
}
externalNativeBuild {
cmake {
path './src/main/cpp/CMakeLists.txt'
}
}
}
dependencies {
// compileOnly dependencies
compileOnly deps.jsr305
compileOnly deps.inferAnnotations
compileOnly 'com.facebook.litho:litho-annotations:0.15.0'
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.facebook.libfb">
</manifest>

View File

@@ -0,0 +1,33 @@
#
# Copyright (c) 2014-present, Facebook, Inc.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#
cmake_minimum_required(VERSION 3.6.0)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fno-omit-frame-pointer
-fexceptions
-Wall
-std=c++11
-DDISABLE_CPUCAP
-DDISABLE_XPLAT)
file(GLOB fb_SRC
*.cpp
jni/*.cpp
lyra/*.cpp)
set(libjnihack_DIR ../../../../jni-hack/)
add_library(fb SHARED
${fb_SRC})
target_include_directories(fb PRIVATE
include ${libjnihack_DIR})
target_link_libraries(fb log)

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <cstdarg>
#include <stdio.h>
#include <fb/assert.h>
#include <fb/log.h>
namespace facebook {
#define ASSERT_BUF_SIZE 4096
static char sAssertBuf[ASSERT_BUF_SIZE];
static AssertHandler gAssertHandler;
void assertInternal(const char* formatstr ...) {
va_list va_args;
va_start(va_args, formatstr);
vsnprintf(sAssertBuf, sizeof(sAssertBuf), formatstr, va_args);
va_end(va_args);
if (gAssertHandler != NULL) {
gAssertHandler(sAssertBuf);
}
FBLOG(LOG_FATAL, "fbassert", "%s", sAssertBuf);
// crash at this specific address so that we can find our crashes easier
*(int*)0xdeadb00c = 0;
// let the compiler know we won't reach the end of the function
__builtin_unreachable();
}
void setAssertHandler(AssertHandler assertHandler) {
gAssertHandler = assertHandler;
}
} // namespace facebook

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#pragma once
#include <jni.h>
#include <fb/Countable.h>
#include <fb/RefPtr.h>
#include <fb/visibility.h>
namespace facebook {
namespace jni {
FBEXPORT const RefPtr<Countable>& countableFromJava(JNIEnv* env, jobject obj);
template <typename T> RefPtr<T> extractRefPtr(JNIEnv* env, jobject obj) {
return static_cast<RefPtr<T>>(countableFromJava(env, obj));
}
template <typename T> RefPtr<T> extractPossiblyNullRefPtr(JNIEnv* env, jobject obj) {
return obj ? extractRefPtr<T>(env, obj) : nullptr;
}
FBEXPORT void setCountableForJava(JNIEnv* env, jobject obj, RefPtr<Countable>&& countable);
void CountableOnLoad(JNIEnv* env);
} }

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#pragma once
#include <memory>
#include <type_traits>
#include <jni.h>
#include <fb/Environment.h>
namespace facebook { namespace jni {
template<typename T>
class GlobalReference {
static_assert(std::is_convertible<T, jobject>::value,
"GlobalReference<T> instantiated with type that is not "
"convertible to jobject");
public:
explicit GlobalReference(T globalReference) :
reference_(globalReference? Environment::current()->NewGlobalRef(globalReference) : nullptr) {
}
~GlobalReference() {
reset();
}
GlobalReference() :
reference_(nullptr) {
}
// enable move constructor and assignment
GlobalReference(GlobalReference&& rhs) :
reference_(std::move(rhs.reference_)) {
rhs.reference_ = nullptr;
}
GlobalReference& operator=(GlobalReference&& rhs) {
if (this != &rhs) {
reset();
reference_ = std::move(rhs.reference_);
rhs.reference_ = nullptr;
}
return *this;
}
GlobalReference(const GlobalReference<T>& rhs) :
reference_{} {
reset(rhs.get());
}
GlobalReference& operator=(const GlobalReference<T>& rhs) {
if (this == &rhs) {
return *this;
}
reset(rhs.get());
return *this;
}
explicit operator bool() const {
return (reference_ != nullptr);
}
T get() const {
return reinterpret_cast<T>(reference_);
}
void reset(T globalReference = nullptr) {
if (reference_) {
Environment::current()->DeleteGlobalRef(reference_);
}
if (globalReference) {
reference_ = Environment::current()->NewGlobalRef(globalReference);
} else {
reference_ = nullptr;
}
}
private:
jobject reference_;
};
}}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#pragma once
#include <memory>
#include <type_traits>
#include <jni.h>
#include <fb/Environment.h>
namespace facebook {
namespace jni {
template<class T>
struct LocalReferenceDeleter {
static_assert(std::is_convertible<T, jobject>::value,
"LocalReferenceDeleter<T> instantiated with type that is not convertible to jobject");
void operator()(T localReference) {
if (localReference != nullptr) {
Environment::current()->DeleteLocalRef(localReference);
}
}
};
template<class T>
using LocalReference =
std::unique_ptr<typename std::remove_pointer<T>::type, LocalReferenceDeleter<T>>;
} }

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#pragma once
#include <string>
#include <jni.h>
#include <fb/visibility.h>
namespace facebook {
namespace jni {
namespace detail {
void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, size_t modifiedLength);
size_t modifiedLength(const std::string& str);
size_t modifiedLength(const uint8_t* str, size_t* length);
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept;
std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len) noexcept;
}
// JNI represents strings encoded with modified version of UTF-8. The difference between UTF-8 and
// Modified UTF-8 is that the latter support only 1-byte, 2-byte, and 3-byte formats. Supplementary
// character (4 bytes in unicode) needs to be represented in the form of surrogate pairs. To create
// a Modified UTF-8 surrogate pair that Dalvik would understand we take 4-byte unicode character,
// encode it with UTF-16 which gives us two 2 byte chars (surrogate pair) and then we encode each
// pair as UTF-8. This result in 2 x 3 byte characters. To convert modified UTF-8 to standard
// UTF-8, this mus tbe reversed.
//
// The second difference is that Modified UTF-8 is encoding NUL byte in 2-byte format.
//
// In order to avoid complex error handling, only a minimum of validity checking is done to avoid
// crashing. If the input is invalid, the output may be invalid as well.
//
// Relevant links:
// - http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html
// - https://docs.oracle.com/javase/6/docs/api/java/io/DataInput.html#modified-utf-8
class FBEXPORT LocalString {
public:
// Assumes UTF8 encoding and make a required convertion to modified UTF-8 when the string
// contains unicode supplementary characters.
explicit LocalString(const std::string& str);
explicit LocalString(const char* str);
jstring string() const {
return m_string;
}
~LocalString();
private:
jstring m_string;
};
// JString to UTF16 extractor using RAII idiom
class JStringUtf16Extractor {
public:
JStringUtf16Extractor(JNIEnv* env, jstring javaString)
: env_(env)
, javaString_(javaString)
, utf16String_(nullptr) {
if (env_ && javaString_) {
utf16String_ = env_->GetStringCritical(javaString_, nullptr);
}
}
~JStringUtf16Extractor() {
if (utf16String_) {
env_->ReleaseStringCritical(javaString_, utf16String_);
}
}
operator const jchar* () const {
return utf16String_;
}
private:
JNIEnv* env_;
jstring javaString_;
const jchar* utf16String_;
};
// The string from JNI is converted to standard UTF-8 if the string contains supplementary
// characters.
FBEXPORT std::string fromJString(JNIEnv* env, jstring str);
} }

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#pragma once
#include <jni.h>
#include <initializer_list>
#include <fb/assert.h>
namespace facebook {
namespace jni {
static inline void registerNatives(JNIEnv* env, jclass cls, std::initializer_list<JNINativeMethod> methods) {
auto result = env->RegisterNatives(cls, methods.begin(), methods.size());
FBASSERT(result == 0);
}
static inline void registerNatives(JNIEnv* env, const char* cls, std::initializer_list<JNINativeMethod> list) {
registerNatives(env, env->FindClass(cls), list);
}
} }

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#pragma once
#include <string>
#include <jni.h>
#include <fb/noncopyable.h>
#include <fb/Countable.h>
#include <fb/visibility.h>
namespace facebook {
namespace jni {
class FBEXPORT WeakReference : public Countable {
public:
typedef RefPtr<WeakReference> Ptr;
WeakReference(jobject strongRef);
~WeakReference();
jweak weakRef() {
return m_weakReference;
}
private:
jweak m_weakReference;
};
// This class is intended to take a weak reference and turn it into a strong
// local reference. Consequently, it should only be allocated on the stack.
class FBEXPORT ResolvedWeakReference : public noncopyable {
public:
ResolvedWeakReference(jobject weakRef);
ResolvedWeakReference(const RefPtr<WeakReference>& weakRef);
~ResolvedWeakReference();
operator jobject () {
return m_strongReference;
}
explicit operator bool () {
return m_strongReference != nullptr;
}
private:
jobject m_strongReference;
};
} }

View File

@@ -0,0 +1,137 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#pragma once
#include <jni.h>
#include <fb/visibility.h>
namespace facebook {
/**
* Instructs the JNI environment to throw an exception.
*
* @param pEnv JNI environment
* @param szClassName class name to throw
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
FBEXPORT jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args);
/**
* Instructs the JNI environment to throw a NoClassDefFoundError.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
FBEXPORT jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...);
/**
* Instructs the JNI environment to throw a RuntimeException.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
FBEXPORT jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...);
/**
* Instructs the JNI environment to throw a IllegalArgumentException.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
FBEXPORT jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...);
/**
* Instructs the JNI environment to throw a IllegalStateException.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
FBEXPORT jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...);
/**
* Instructs the JNI environment to throw an IOException.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
FBEXPORT jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...);
/**
* Instructs the JNI environment to throw an AssertionError.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
FBEXPORT jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...);
/**
* Instructs the JNI environment to throw an OutOfMemoryError.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
FBEXPORT jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...);
/**
* Finds the specified class. If it's not found, instructs the JNI environment to throw an
* exception.
*
* @param pEnv JNI environment
* @param szClassName the classname to find in JNI format (e.g. "java/lang/String")
* @return the class or NULL if not found (in which case a pending exception will be queued). This
* returns a global reference (JNIEnv::NewGlobalRef).
*/
FBEXPORT jclass findClassOrThrow(JNIEnv *pEnv, const char* szClassName);
/**
* Finds the specified field of the specified class. If it's not found, instructs the JNI
* environment to throw an exception.
*
* @param pEnv JNI environment
* @param clazz the class to lookup the field in
* @param szFieldName the name of the field to find
* @param szSig the signature of the field
* @return the field or NULL if not found (in which case a pending exception will be queued)
*/
FBEXPORT jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig);
/**
* Finds the specified method of the specified class. If it's not found, instructs the JNI
* environment to throw an exception.
*
* @param pEnv JNI environment
* @param clazz the class to lookup the method in
* @param szMethodName the name of the method to find
* @param szSig the signature of the method
* @return the method or NULL if not found (in which case a pending exception will be queued)
*/
FBEXPORT jmethodID getMethodIdOrThrow(
JNIEnv* pEnv,
jclass clazz,
const char* szMethodName,
const char* szSig);
} // namespace facebook

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2016-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <fb/fbjni/ByteBuffer.h>
#include <stdexcept>
#include <fb/fbjni/References.h>
namespace facebook {
namespace jni {
namespace {
local_ref<JByteBuffer> createEmpty() {
static auto cls = JByteBuffer::javaClassStatic();
static auto meth = cls->getStaticMethod<JByteBuffer::javaobject(int)>("allocateDirect");
return meth(cls, 0);
}
}
local_ref<JByteBuffer> JByteBuffer::wrapBytes(uint8_t* data, size_t size) {
// env->NewDirectByteBuffer requires that size is positive. Android's
// dalvik returns an invalid result and Android's art aborts if size == 0.
// Workaround this by using a slow path through Java in that case.
if (!size) {
return createEmpty();
}
auto res = adopt_local(static_cast<javaobject>(Environment::current()->NewDirectByteBuffer(data, size)));
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (!res) {
throw std::runtime_error("Direct byte buffers are unsupported.");
}
return res;
}
uint8_t* JByteBuffer::getDirectBytes() const {
if (!self()) {
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
}
void* bytes = Environment::current()->GetDirectBufferAddress(self());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (!bytes) {
throw std::runtime_error(
isDirect() ?
"Attempt to get direct bytes of non-direct byte buffer." :
"Error getting direct bytes of byte buffer.");
}
return static_cast<uint8_t*>(bytes);
}
size_t JByteBuffer::getDirectSize() const {
if (!self()) {
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
}
int size = Environment::current()->GetDirectBufferCapacity(self());
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (size < 0) {
throw std::runtime_error(
isDirect() ?
"Attempt to get direct size of non-direct byte buffer." :
"Error getting direct size of byte buffer.");
}
return static_cast<size_t>(size);
}
bool JByteBuffer::isDirect() const {
static auto meth = javaClassStatic()->getMethod<jboolean()>("isDirect");
return meth(self());
}
}}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <cstdint>
#include <jni/Countable.h>
#include <fb/Environment.h>
#include <jni/Registration.h>
namespace facebook {
namespace jni {
static jfieldID gCountableNativePtr;
static RefPtr<Countable>* rawCountableFromJava(JNIEnv* env, jobject obj) {
FBASSERT(obj);
return reinterpret_cast<RefPtr<Countable>*>(env->GetLongField(obj, gCountableNativePtr));
}
const RefPtr<Countable>& countableFromJava(JNIEnv* env, jobject obj) {
FBASSERT(obj);
return *rawCountableFromJava(env, obj);
}
void setCountableForJava(JNIEnv* env, jobject obj, RefPtr<Countable>&& countable) {
int oldValue = env->GetLongField(obj, gCountableNativePtr);
FBASSERTMSGF(oldValue == 0, "Cannot reinitialize object; expected nullptr, got %x", oldValue);
FBASSERT(countable);
uintptr_t fieldValue = (uintptr_t) new RefPtr<Countable>(std::move(countable));
env->SetLongField(obj, gCountableNativePtr, fieldValue);
}
/**
* NB: THREAD SAFETY (this comment also exists at Countable.java)
*
* This method deletes the corresponding native object on whatever thread the method is called
* on. In the common case when this is called by Countable#finalize(), this will be called on the
* system finalizer thread. If you manually call dispose on the Java object, the native object
* will be deleted synchronously on that thread.
*/
void dispose(JNIEnv* env, jobject obj) {
// Grab the pointer
RefPtr<Countable>* countable = rawCountableFromJava(env, obj);
if (!countable) {
// That was easy.
return;
}
// Clear out the old value to avoid double-frees
env->SetLongField(obj, gCountableNativePtr, 0);
delete countable;
}
void CountableOnLoad(JNIEnv* env) {
jclass countable = env->FindClass("com/facebook/jni/Countable");
gCountableNativePtr = env->GetFieldID(countable, "mInstance", "J");
registerNatives(env, countable, {
{ "dispose", "()V", (void*) dispose },
});
}
} }

View File

@@ -0,0 +1,130 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <pthread.h>
#include <fb/log.h>
#include <fb/StaticInitialized.h>
#include <fb/ThreadLocal.h>
#include <fb/Environment.h>
#include <fb/fbjni/CoreClasses.h>
#include <fb/fbjni/NativeRunnable.h>
#include <functional>
namespace facebook {
namespace jni {
namespace {
StaticInitialized<ThreadLocal<JNIEnv>> g_env;
JavaVM* g_vm = nullptr;
struct JThreadScopeSupport : JavaClass<JThreadScopeSupport> {
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/ThreadScopeSupport;";
// These reinterpret_casts are a totally dangerous pattern. Don't use them. Use HybridData instead.
static void runStdFunction(std::function<void()>&& func) {
static auto method = javaClassStatic()->getStaticMethod<void(jlong)>("runStdFunction");
method(javaClassStatic(), reinterpret_cast<jlong>(&func));
}
static void runStdFunctionImpl(alias_ref<JClass>, jlong ptr) {
(*reinterpret_cast<std::function<void()>*>(ptr))();
}
static void OnLoad() {
// We need the javaClassStatic so that the class lookup is cached and that
// runStdFunction can be called from a ThreadScope-attached thread.
javaClassStatic()->registerNatives({
makeNativeMethod("runStdFunctionImpl", runStdFunctionImpl),
});
}
};
}
/* static */
JNIEnv* Environment::current() {
JNIEnv* env = g_env->get();
if ((env == nullptr) && (g_vm != nullptr)) {
if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM");
// TODO(cjhopman): This should throw an exception.
env = nullptr;
} else {
g_env->reset(env);
}
}
return env;
}
/* static */
void Environment::detachCurrentThread() {
auto env = g_env->get();
if (env) {
FBASSERT(g_vm);
g_vm->DetachCurrentThread();
g_env->reset();
}
}
struct EnvironmentInitializer {
EnvironmentInitializer(JavaVM* vm) {
FBASSERT(!g_vm);
FBASSERT(vm);
g_vm = vm;
g_env.initialize([] (void*) {});
}
};
/* static */
void Environment::initialize(JavaVM* vm) {
static EnvironmentInitializer init(vm);
}
/* static */
JNIEnv* Environment::ensureCurrentThreadIsAttached() {
auto env = g_env->get();
if (!env) {
FBASSERT(g_vm);
g_vm->AttachCurrentThread(&env, nullptr);
g_env->reset(env);
}
return env;
}
ThreadScope::ThreadScope()
: attachedWithThisScope_(false) {
JNIEnv* env = nullptr;
if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_EDETACHED) {
return;
}
env = facebook::jni::Environment::ensureCurrentThreadIsAttached();
FBASSERT(env);
attachedWithThisScope_ = true;
}
ThreadScope::~ThreadScope() {
if (attachedWithThisScope_) {
Environment::detachCurrentThread();
}
}
/* static */
void ThreadScope::OnLoad() {
// These classes are required for ScopeWithClassLoader. Ensure they are looked up when loading.
JThreadScopeSupport::OnLoad();
}
/* static */
void ThreadScope::WithClassLoader(std::function<void()>&& runnable) {
// TODO(cjhopman): If the classloader is already available in this scope, we
// shouldn't have to jump through java.
ThreadScope ts;
JThreadScopeSupport::runStdFunction(std::move(runnable));
}
} }

View File

@@ -0,0 +1,283 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <fb/fbjni/CoreClasses.h>
#include <fb/assert.h>
#include <fb/log.h>
#include <alloca.h>
#include <cstdlib>
#include <ios>
#include <stdexcept>
#include <stdio.h>
#include <string>
#include <system_error>
#include <jni.h>
namespace facebook {
namespace jni {
namespace {
class JRuntimeException : public JavaClass<JRuntimeException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;";
static local_ref<JRuntimeException> create(const char* str) {
return newInstance(make_jstring(str));
}
static local_ref<JRuntimeException> create() {
return newInstance();
}
};
class JIOException : public JavaClass<JIOException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Ljava/io/IOException;";
static local_ref<JIOException> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JOutOfMemoryError : public JavaClass<JOutOfMemoryError, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;";
static local_ref<JOutOfMemoryError> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JArrayIndexOutOfBoundsException : public JavaClass<JArrayIndexOutOfBoundsException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;";
static local_ref<JArrayIndexOutOfBoundsException> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JUnknownCppException : public JavaClass<JUnknownCppException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;";
static local_ref<JUnknownCppException> create() {
return newInstance();
}
static local_ref<JUnknownCppException> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JCppSystemErrorException : public JavaClass<JCppSystemErrorException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;";
static local_ref<JCppSystemErrorException> create(const std::system_error& e) {
return newInstance(make_jstring(e.what()), e.code().value());
}
};
// Exception throwing & translating functions //////////////////////////////////////////////////////
// Functions that throw Java exceptions
void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) {
auto env = Environment::current();
if (throwable) {
env->Throw(throwable.get());
}
if (env->ExceptionCheck() != JNI_TRUE) {
std::abort();
}
}
}
// Functions that throw C++ exceptions
// TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated
void throwPendingJniExceptionAsCppException() {
JNIEnv* env = Environment::current();
if (env->ExceptionCheck() == JNI_FALSE) {
return;
}
auto throwable = adopt_local(env->ExceptionOccurred());
if (!throwable) {
throw std::runtime_error("Unable to get pending JNI exception.");
}
env->ExceptionClear();
throw JniException(throwable);
}
void throwCppExceptionIf(bool condition) {
if (!condition) {
return;
}
auto env = Environment::current();
if (env->ExceptionCheck() == JNI_TRUE) {
throwPendingJniExceptionAsCppException();
return;
}
throw JniException();
}
void throwNewJavaException(jthrowable throwable) {
throw JniException(wrap_alias(throwable));
}
void throwNewJavaException(const char* throwableName, const char* msg) {
// If anything of the fbjni calls fail, an exception of a suitable
// form will be thrown, which is what we want.
auto throwableClass = findClassLocal(throwableName);
auto throwable = throwableClass->newObject(
throwableClass->getConstructor<jthrowable(jstring)>(),
make_jstring(msg).release());
throwNewJavaException(throwable.get());
}
// Translate C++ to Java Exception
namespace {
// The implementation std::rethrow_if_nested uses a dynamic_cast to determine
// if the exception is a nested_exception. If the exception is from a library
// built with -fno-rtti, then that will crash. This avoids that.
void rethrow_if_nested() {
try {
throw;
} catch (const std::nested_exception& e) {
e.rethrow_nested();
} catch (...) {
}
}
// For each exception in the chain of the currently handled exception, func
// will be called with that exception as the currently handled exception (in
// reverse order, i.e. innermost first).
void denest(std::function<void()> func) {
try {
throw;
} catch (const std::exception& e) {
try {
rethrow_if_nested();
} catch (...) {
denest(func);
}
func();
} catch (...) {
func();
}
}
}
void translatePendingCppExceptionToJavaException() noexcept {
local_ref<JThrowable> previous;
auto func = [&previous] () {
local_ref<JThrowable> current;
try {
throw;
} catch(const JniException& ex) {
current = ex.getThrowable();
} catch(const std::ios_base::failure& ex) {
current = JIOException::create(ex.what());
} catch(const std::bad_alloc& ex) {
current = JOutOfMemoryError::create(ex.what());
} catch(const std::out_of_range& ex) {
current = JArrayIndexOutOfBoundsException::create(ex.what());
} catch(const std::system_error& ex) {
current = JCppSystemErrorException::create(ex);
} catch(const std::runtime_error& ex) {
current = JRuntimeException::create(ex.what());
} catch(const std::exception& ex) {
current = JCppException::create(ex.what());
} catch(const char* msg) {
current = JUnknownCppException::create(msg);
} catch(...) {
current = JUnknownCppException::create();
}
if (previous) {
current->initCause(previous);
}
previous = current;
};
try {
denest(func);
setJavaExceptionAndAbortOnFailure(previous);
} catch (std::exception& e) {
FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException: %s", e.what());
// rethrow the exception and let the noexcept handling abort.
throw;
} catch (...) {
FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException");
throw;
}
}
// JniException ////////////////////////////////////////////////////////////////////////////////////
const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message.";
JniException::JniException() : JniException(JRuntimeException::create()) { }
JniException::JniException(alias_ref<jthrowable> throwable) : isMessageExtracted_(false) {
throwable_ = make_global(throwable);
}
JniException::JniException(JniException &&rhs)
: throwable_(std::move(rhs.throwable_)),
what_(std::move(rhs.what_)),
isMessageExtracted_(rhs.isMessageExtracted_) {
}
JniException::JniException(const JniException &rhs)
: what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) {
throwable_ = make_global(rhs.throwable_);
}
JniException::~JniException() {
ThreadScope ts;
throwable_.reset();
}
local_ref<JThrowable> JniException::getThrowable() const noexcept {
return make_local(throwable_);
}
// TODO 6900503: consider making this thread-safe.
void JniException::populateWhat() const noexcept {
ThreadScope ts;
try {
what_ = throwable_->toString();
isMessageExtracted_ = true;
} catch(...) {
what_ = kExceptionMessageFailure_;
}
}
const char* JniException::what() const noexcept {
if (!isMessageExtracted_) {
populateWhat();
}
return what_.c_str();
}
void JniException::setJavaException() const noexcept {
setJavaExceptionAndAbortOnFailure(throwable_);
}
}}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include "fb/fbjni.h"
namespace facebook {
namespace jni {
namespace detail {
void HybridData::setNativePointer(std::unique_ptr<BaseHybridClass> new_value) {
static auto pointerField = getClass()->getField<jlong>("mNativePointer");
auto* old_value = reinterpret_cast<BaseHybridClass*>(getFieldValue(pointerField));
if (new_value) {
// Modify should only ever be called once with a non-null
// new_value. If this happens again it's a programmer error, so
// blow up.
FBASSERTMSGF(old_value == 0, "Attempt to set C++ native pointer twice");
} else if (old_value == 0) {
return;
}
// delete on a null pointer is defined to be a noop.
delete old_value;
// This releases ownership from the unique_ptr, and passes the pointer, and
// ownership of it, to HybridData which is managed by the java GC. The
// finalizer on hybridData calls resetNative which will delete the object, if
// resetNative has not already been called.
setFieldValue(pointerField, reinterpret_cast<jlong>(new_value.release()));
}
BaseHybridClass* HybridData::getNativePointer() {
static auto pointerField = getClass()->getField<jlong>("mNativePointer");
auto* value = reinterpret_cast<BaseHybridClass*>(getFieldValue(pointerField));
if (!value) {
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
}
return value;
}
local_ref<HybridData> HybridData::create() {
return newInstance();
}
}
namespace {
void resetNative(alias_ref<detail::HybridData> jthis) {
jthis->setNativePointer(nullptr);
}
}
void HybridDataOnLoad() {
registerNatives("com/facebook/jni/HybridData", {
makeNativeMethod("resetNative", resetNative),
});
}
}}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include "fb/fbjni.h"
#include <sstream>
std::string JThrowable::getStackTrace() const {
static auto getStackTraceMethod = javaClassStatic()
->getMethod<facebook::jni::local_ref<facebook::jni::JThrowable::JStackTrace>()>("getStackTrace");
std::ostringstream os;
auto stackTrace = getStackTraceMethod(self());
for (size_t i = 0; i < stackTrace->size(); ++i) {
os << facebook::jni::adopt_local((*stackTrace)[i])->toString() << ' ';
}
return os.str();
}

View File

@@ -0,0 +1,310 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <jni/LocalString.h>
#include <fb/Environment.h>
#include <fb/assert.h>
#include <vector>
namespace facebook {
namespace jni {
namespace {
const uint16_t kUtf8OneByteBoundary = 0x80;
const uint16_t kUtf8TwoBytesBoundary = 0x800;
const uint16_t kUtf16HighSubLowBoundary = 0xD800;
const uint16_t kUtf16HighSubHighBoundary = 0xDC00;
const uint16_t kUtf16LowSubHighBoundary = 0xE000;
inline void encode3ByteUTF8(char32_t code, uint8_t* out) {
FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits");
out[0] = 0xE0 | (code >> 12);
out[1] = 0x80 | ((code >> 6) & 0x3F);
out[2] = 0x80 | (code & 0x3F);
}
inline char32_t decode3ByteUTF8(const uint8_t* in) {
return (((in[0] & 0x0f) << 12) |
((in[1] & 0x3f) << 6) |
( in[2] & 0x3f));
}
inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) {
FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits");
out[offset] = (char) (0xF0 | (code >> 18));
out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F));
out[offset + 2] = (char) (0x80 | ((code >> 6) & 0x3F));
out[offset + 3] = (char) (0x80 | (code & 0x3F));
}
template <typename T>
inline bool isFourByteUTF8Encoding(const T* utf8) {
return ((*utf8 & 0xF8) == 0xF0);
}
}
namespace detail {
size_t modifiedLength(const std::string& str) {
// Scan for supplementary characters
size_t j = 0;
for (size_t i = 0; i < str.size(); ) {
if (str[i] == 0) {
i += 1;
j += 2;
} else if (i + 4 > str.size() ||
!isFourByteUTF8Encoding(&(str[i]))) {
// See the code in utf8ToModifiedUTF8 for what's happening here.
i += 1;
j += 1;
} else {
i += 4;
j += 6;
}
}
return j;
}
// returns modified utf8 length; *length is set to strlen(str)
size_t modifiedLength(const uint8_t* str, size_t* length) {
// NUL-terminated: Scan for length and supplementary characters
size_t i = 0;
size_t j = 0;
while (str[i] != 0) {
if (str[i + 1] == 0 ||
str[i + 2] == 0 ||
str[i + 3] == 0 ||
!isFourByteUTF8Encoding(&(str[i]))) {
i += 1;
j += 1;
} else {
i += 4;
j += 6;
}
}
*length = i;
return j;
}
void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size_t modifiedBufLen)
{
size_t j = 0;
for (size_t i = 0; i < len; ) {
FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
if (utf8[i] == 0) {
FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short");
modified[j] = 0xc0;
modified[j + 1] = 0x80;
i += 1;
j += 2;
continue;
}
if (i + 4 > len ||
!isFourByteUTF8Encoding(utf8 + i)) {
// If the input is too short for this to be a four-byte
// encoding, or it isn't one for real, just copy it on through.
modified[j] = utf8[i];
i++;
j++;
continue;
}
// Convert 4 bytes of input to 2 * 3 bytes of output
char32_t code = (((utf8[i] & 0x07) << 18) |
((utf8[i + 1] & 0x3f) << 12) |
((utf8[i + 2] & 0x3f) << 6) |
( utf8[i + 3] & 0x3f));
char32_t first;
char32_t second;
if (code > 0x10ffff) {
// These could be valid utf-8, but cannot be represented as modified UTF-8, due to the 20-bit
// limit on that representation. Encode two replacement characters, so the expected output
// length lines up.
const char32_t kUnicodeReplacementChar = 0xfffd;
first = kUnicodeReplacementChar;
second = kUnicodeReplacementChar;
} else {
// split into surrogate pair
first = ((code - 0x010000) >> 10) | 0xd800;
second = ((code - 0x010000) & 0x3ff) | 0xdc00;
}
// encode each as a 3 byte surrogate value
FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short");
encode3ByteUTF8(first, modified + j);
encode3ByteUTF8(second, modified + j + 3);
i += 4;
j += 6;
}
FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
modified[j++] = '\0';
}
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept {
// Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient
std::string utf8(len, 0);
size_t j = 0;
for (size_t i = 0; i < len; ) {
// surrogate pair: 1101 10xx xxxx xxxx 1101 11xx xxxx xxxx
// encoded pair: 1110 1101 1010 xxxx 10xx xxxx 1110 1101 1011 xxxx 10xx xxxx
if (len >= i + 6 &&
modified[i] == 0xed &&
(modified[i + 1] & 0xf0) == 0xa0 &&
modified[i + 3] == 0xed &&
(modified[i + 4] & 0xf0) == 0xb0) {
// Valid surrogate pair
char32_t pair1 = decode3ByteUTF8(modified + i);
char32_t pair2 = decode3ByteUTF8(modified + i + 3);
char32_t ch = 0x10000 + (((pair1 & 0x3ff) << 10) |
( pair2 & 0x3ff));
encode4ByteUTF8(ch, utf8, j);
i += 6;
j += 4;
continue;
} else if (len >= i + 2 &&
modified[i] == 0xc0 &&
modified[i + 1] == 0x80) {
utf8[j] = 0;
i += 2;
j += 1;
continue;
}
// copy one byte. This might be a one, two, or three-byte encoding. It might be an invalid
// encoding of some sort, but garbage in garbage out is ok.
utf8[j] = (char) modified[i];
i++;
j++;
}
utf8.resize(j);
return utf8;
}
// Calculate how many bytes are needed to convert an UTF16 string into UTF8
// UTF16 string
size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
if (!utf16String || utf16StringLen == 0) {
return 0;
}
uint32_t utf8StringLen = 0;
auto utf16StringEnd = utf16String + utf16StringLen;
auto idx16 = utf16String;
while (idx16 < utf16StringEnd) {
auto ch = *idx16++;
if (ch < kUtf8OneByteBoundary) {
utf8StringLen++;
} else if (ch < kUtf8TwoBytesBoundary) {
utf8StringLen += 2;
} else if (
(ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
(idx16 < utf16StringEnd) &&
(*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
utf8StringLen += 4;
idx16++;
} else {
utf8StringLen += 3;
}
}
return utf8StringLen;
}
std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept {
if (!utf16String || utf16StringLen <= 0) {
return "";
}
std::string utf8String(utf16toUTF8Length(utf16String, utf16StringLen), '\0');
auto idx8 = utf8String.begin();
auto idx16 = utf16String;
auto utf16StringEnd = utf16String + utf16StringLen;
while (idx16 < utf16StringEnd) {
auto ch = *idx16++;
if (ch < kUtf8OneByteBoundary) {
*idx8++ = (ch & 0x7F);
} else if (ch < kUtf8TwoBytesBoundary) {
*idx8++ = 0b11000000 | (ch >> 6);
*idx8++ = 0b10000000 | (ch & 0x3F);
} else if (
(ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
(idx16 < utf16StringEnd) &&
(*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
auto ch2 = *idx16++;
uint8_t trunc_byte = (((ch >> 6) & 0x0F) + 1);
*idx8++ = 0b11110000 | (trunc_byte >> 2);
*idx8++ = 0b10000000 | ((trunc_byte & 0x03) << 4) | ((ch >> 2) & 0x0F);
*idx8++ = 0b10000000 | ((ch & 0x03) << 4) | ((ch2 >> 6) & 0x0F);
*idx8++ = 0b10000000 | (ch2 & 0x3F);
} else {
*idx8++ = 0b11100000 | (ch >> 12);
*idx8++ = 0b10000000 | ((ch >> 6) & 0x3F);
*idx8++ = 0b10000000 | (ch & 0x3F);
}
}
return utf8String;
}
}
LocalString::LocalString(const std::string& str)
{
size_t modlen = detail::modifiedLength(str);
if (modlen == str.size()) {
// no supplementary characters, build jstring from input buffer
m_string = Environment::current()->NewStringUTF(str.data());
return;
}
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
detail::utf8ToModifiedUTF8(
reinterpret_cast<const uint8_t*>(str.data()), str.size(),
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
m_string = Environment::current()->NewStringUTF(modified.data());
}
LocalString::LocalString(const char* str)
{
size_t len;
size_t modlen = detail::modifiedLength(reinterpret_cast<const uint8_t*>(str), &len);
if (modlen == len) {
// no supplementary characters, build jstring from input buffer
m_string = Environment::current()->NewStringUTF(str);
return;
}
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
detail::utf8ToModifiedUTF8(
reinterpret_cast<const uint8_t*>(str), len,
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
m_string = Environment::current()->NewStringUTF(modified.data());
}
LocalString::~LocalString() {
Environment::current()->DeleteLocalRef(m_string);
}
std::string fromJString(JNIEnv* env, jstring str) {
auto utf16String = JStringUtf16Extractor(env, str);
auto length = env->GetStringLength(str);
return detail::utf16toUTF8(utf16String, length);
}
} }

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <jni/Countable.h>
#include <fb/Environment.h>
#include <fb/fbjni.h>
#include <fb/fbjni/NativeRunnable.h>
using namespace facebook::jni;
void initialize_fbjni() {
CountableOnLoad(Environment::current());
HybridDataOnLoad();
JNativeRunnable::OnLoad();
ThreadScope::OnLoad();
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <fb/fbjni/References.h>
namespace facebook {
namespace jni {
JniLocalScope::JniLocalScope(JNIEnv* env, jint capacity)
: env_(env) {
hasFrame_ = false;
auto pushResult = env->PushLocalFrame(capacity);
FACEBOOK_JNI_THROW_EXCEPTION_IF(pushResult < 0);
hasFrame_ = true;
}
JniLocalScope::~JniLocalScope() {
if (hasFrame_) {
env_->PopLocalFrame(nullptr);
}
}
namespace internal {
// Default implementation always returns true.
// Platform-specific sources can override this.
bool doesGetObjectRefTypeWork() __attribute__ ((weak));
bool doesGetObjectRefTypeWork() {
return true;
}
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <fb/Environment.h>
#include <jni/WeakReference.h>
namespace facebook {
namespace jni {
WeakReference::WeakReference(jobject strongRef) :
m_weakReference(Environment::current()->NewWeakGlobalRef(strongRef))
{
}
WeakReference::~WeakReference() {
auto env = Environment::current();
FBASSERTMSGF(env, "Attempt to delete jni::WeakReference from non-JNI thread");
env->DeleteWeakGlobalRef(m_weakReference);
}
ResolvedWeakReference::ResolvedWeakReference(jobject weakRef) :
m_strongReference(Environment::current()->NewLocalRef(weakRef))
{
}
ResolvedWeakReference::ResolvedWeakReference(const RefPtr<WeakReference>& weakRef) :
m_strongReference(Environment::current()->NewLocalRef(weakRef->weakRef()))
{
}
ResolvedWeakReference::~ResolvedWeakReference() {
if (m_strongReference)
Environment::current()->DeleteLocalRef(m_strongReference);
}
} }

View File

@@ -0,0 +1,194 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <fb/fbjni.h>
#include <mutex>
#include <vector>
#include <jni/LocalString.h>
#include <fb/log.h>
namespace facebook {
namespace jni {
jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
static std::once_flag flag{};
// TODO (t7832883): DTRT when we have exception pointers
static auto error_msg = std::string{"Failed to initialize fbjni"};
static auto error_occured = false;
std::call_once(flag, [vm] {
try {
Environment::initialize(vm);
} catch (std::exception& ex) {
error_occured = true;
try {
error_msg = std::string{"Failed to initialize fbjni: "} + ex.what();
} catch (...) {
// Ignore, we already have a fall back message
}
} catch (...) {
error_occured = true;
}
});
try {
if (error_occured) {
throw std::runtime_error(error_msg);
}
init_fn();
} catch (const std::exception& e) {
FBLOGE("error %s", e.what());
translatePendingCppExceptionToJavaException();
} catch (...) {
translatePendingCppExceptionToJavaException();
// So Java will handle the translated exception, fall through and
// return a good version number.
}
return JNI_VERSION_1_6;
}
alias_ref<JClass> findClassStatic(const char* name) {
const auto env = internal::getEnv();
if (!env) {
throw std::runtime_error("Unable to retrieve JNIEnv*.");
}
auto cls = env->FindClass(name);
FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
auto leaking_ref = (jclass)env->NewGlobalRef(cls);
FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref);
return wrap_alias(leaking_ref);
}
local_ref<JClass> findClassLocal(const char* name) {
const auto env = internal::getEnv();
if (!env) {
throw std::runtime_error("Unable to retrieve JNIEnv*.");
}
auto cls = env->FindClass(name);
FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
return adopt_local(cls);
}
// jstring /////////////////////////////////////////////////////////////////////////////////////////
std::string JString::toStdString() const {
const auto env = internal::getEnv();
auto utf16String = JStringUtf16Extractor(env, self());
auto length = env->GetStringLength(self());
return detail::utf16toUTF8(utf16String, length);
}
local_ref<JString> make_jstring(const char* utf8) {
if (!utf8) {
return {};
}
const auto env = internal::getEnv();
size_t len;
size_t modlen = detail::modifiedLength(reinterpret_cast<const uint8_t*>(utf8), &len);
jstring result;
if (modlen == len) {
// The only difference between utf8 and modifiedUTF8 is in encoding 4-byte UTF8 chars
// and '\0' that is encoded on 2 bytes.
//
// Since modifiedUTF8-encoded string can be no shorter than it's UTF8 conterpart we
// know that if those two strings are of the same length we don't need to do any
// conversion -> no 4-byte chars nor '\0'.
result = env->NewStringUTF(utf8);
} else {
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
detail::utf8ToModifiedUTF8(
reinterpret_cast<const uint8_t*>(utf8), len,
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
result = env->NewStringUTF(modified.data());
}
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return adopt_local(result);
}
// JniPrimitiveArrayFunctions //////////////////////////////////////////////////////////////////////
#pragma push_macro("DEFINE_PRIMITIVE_METHODS")
#undef DEFINE_PRIMITIVE_METHODS
#define DEFINE_PRIMITIVE_METHODS(TYPE, NAME, SMALLNAME) \
\
template<> \
FBEXPORT \
TYPE* JPrimitiveArray<TYPE ## Array>::getElements(jboolean* isCopy) { \
auto env = internal::getEnv(); \
TYPE* res = env->Get ## NAME ## ArrayElements(self(), isCopy); \
FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
return res; \
} \
\
template<> \
FBEXPORT \
void JPrimitiveArray<TYPE ## Array>::releaseElements( \
TYPE* elements, jint mode) { \
auto env = internal::getEnv(); \
env->Release ## NAME ## ArrayElements(self(), elements, mode); \
FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
} \
\
template<> \
FBEXPORT \
void JPrimitiveArray<TYPE ## Array>::getRegion( \
jsize start, jsize length, TYPE* buf) { \
auto env = internal::getEnv(); \
env->Get ## NAME ## ArrayRegion(self(), start, length, buf); \
FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
} \
\
template<> \
FBEXPORT \
void JPrimitiveArray<TYPE ## Array>::setRegion( \
jsize start, jsize length, const TYPE* elements) { \
auto env = internal::getEnv(); \
env->Set ## NAME ## ArrayRegion(self(), start, length, elements); \
FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
} \
\
FBEXPORT \
local_ref<TYPE ## Array> make_ ## SMALLNAME ## _array(jsize size) { \
auto array = internal::getEnv()->New ## NAME ## Array(size); \
FACEBOOK_JNI_THROW_EXCEPTION_IF(!array); \
return adopt_local(array); \
} \
\
template<> \
FBEXPORT \
local_ref<TYPE ## Array> JArray ## NAME::newArray(size_t count) { \
return make_ ## SMALLNAME ## _array(count); \
} \
\
DEFINE_PRIMITIVE_METHODS(jboolean, Boolean, boolean)
DEFINE_PRIMITIVE_METHODS(jbyte, Byte, byte)
DEFINE_PRIMITIVE_METHODS(jchar, Char, char)
DEFINE_PRIMITIVE_METHODS(jshort, Short, short)
DEFINE_PRIMITIVE_METHODS(jint, Int, int)
DEFINE_PRIMITIVE_METHODS(jlong, Long, long)
DEFINE_PRIMITIVE_METHODS(jfloat, Float, float)
DEFINE_PRIMITIVE_METHODS(jdouble, Double, double)
#pragma pop_macro("DEFINE_PRIMITIVE_METHODS")
// Internal debug /////////////////////////////////////////////////////////////////////////////////
namespace internal {
FBEXPORT ReferenceStats g_reference_stats;
FBEXPORT void facebook::jni::internal::ReferenceStats::reset() noexcept {
locals_deleted = globals_deleted = weaks_deleted = 0;
}
}
}}

View File

@@ -0,0 +1,195 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <jni.h>
#include <stddef.h>
#include <cstdio>
#include <jni/jni_helpers.h>
#define MSG_SIZE 1024
namespace facebook {
/**
* Instructs the JNI environment to throw an exception.
*
* @param pEnv JNI environment
* @param szClassName class name to throw
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args) {
char szMsg[MSG_SIZE];
vsnprintf(szMsg, MSG_SIZE, szFmt, va_args);
jclass exClass = pEnv->FindClass(szClassName);
return pEnv->ThrowNew(exClass, szMsg);
}
/**
* Instructs the JNI environment to throw a NoClassDefFoundError.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...) {
va_list va_args;
va_start(va_args, szFmt);
jint ret = throwException(pEnv, "java/lang/NoClassDefFoundError", szFmt, va_args);
va_end(va_args);
return ret;
}
/**
* Instructs the JNI environment to throw a RuntimeException.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...) {
va_list va_args;
va_start(va_args, szFmt);
jint ret = throwException(pEnv, "java/lang/RuntimeException", szFmt, va_args);
va_end(va_args);
return ret;
}
/**
* Instructs the JNI environment to throw an IllegalArgumentException.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...) {
va_list va_args;
va_start(va_args, szFmt);
jint ret = throwException(pEnv, "java/lang/IllegalArgumentException", szFmt, va_args);
va_end(va_args);
return ret;
}
/**
* Instructs the JNI environment to throw an IllegalStateException.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...) {
va_list va_args;
va_start(va_args, szFmt);
jint ret = throwException(pEnv, "java/lang/IllegalStateException", szFmt, va_args);
va_end(va_args);
return ret;
}
/**
* Instructs the JNI environment to throw an OutOfMemoryError.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...) {
va_list va_args;
va_start(va_args, szFmt);
jint ret = throwException(pEnv, "java/lang/OutOfMemoryError", szFmt, va_args);
va_end(va_args);
return ret;
}
/**
* Instructs the JNI environment to throw an AssertionError.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...) {
va_list va_args;
va_start(va_args, szFmt);
jint ret = throwException(pEnv, "java/lang/AssertionError", szFmt, va_args);
va_end(va_args);
return ret;
}
/**
* Instructs the JNI environment to throw an IOException.
*
* @param pEnv JNI environment
* @param szFmt sprintf-style format string
* @param ... sprintf-style args
* @return 0 on success; a negative value on failure
*/
jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...) {
va_list va_args;
va_start(va_args, szFmt);
jint ret = throwException(pEnv, "java/io/IOException", szFmt, va_args);
va_end(va_args);
return ret;
}
/**
* Finds the specified class. If it's not found, instructs the JNI environment to throw an
* exception.
*
* @param pEnv JNI environment
* @param szClassName the classname to find in JNI format (e.g. "java/lang/String")
* @return the class or NULL if not found (in which case a pending exception will be queued). This
* returns a global reference (JNIEnv::NewGlobalRef).
*/
jclass findClassOrThrow(JNIEnv* pEnv, const char* szClassName) {
jclass clazz = pEnv->FindClass(szClassName);
if (!clazz) {
return NULL;
}
return (jclass) pEnv->NewGlobalRef(clazz);
}
/**
* Finds the specified field of the specified class. If it's not found, instructs the JNI
* environment to throw an exception.
*
* @param pEnv JNI environment
* @param clazz the class to lookup the field in
* @param szFieldName the name of the field to find
* @param szSig the signature of the field
* @return the field or NULL if not found (in which case a pending exception will be queued)
*/
jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig) {
return pEnv->GetFieldID(clazz, szFieldName, szSig);
}
/**
* Finds the specified method of the specified class. If it's not found, instructs the JNI
* environment to throw an exception.
*
* @param pEnv JNI environment
* @param clazz the class to lookup the method in
* @param szMethodName the name of the method to find
* @param szSig the signature of the method
* @return the method or NULL if not found (in which case a pending exception will be queued)
*/
jmethodID getMethodIdOrThrow(
JNIEnv* pEnv,
jclass clazz,
const char* szMethodName,
const char* szSig) {
return pEnv->GetMethodID(clazz, szMethodName, szSig);
}
} // namespace facebook

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <fb/log.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#define LOG_BUFFER_SIZE 4096
static LogHandler gLogHandler;
void setLogHandler(LogHandler logHandler) {
gLogHandler = logHandler;
}
int fb_printLog(int prio, const char *tag, const char *fmt, ...) {
char logBuffer[LOG_BUFFER_SIZE];
va_list va_args;
va_start(va_args, fmt);
int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args);
va_end(va_args);
if (gLogHandler != NULL) {
gLogHandler(prio, tag, logBuffer);
}
__android_log_write(prio, tag, logBuffer);
return result;
}
void logPrintByDelims(int priority, const char* tag, const char* delims,
const char* msg, ...)
{
va_list ap;
char buf[32768];
char* context;
char* tok;
va_start(ap, msg);
vsnprintf(buf, sizeof(buf), msg, ap);
va_end(ap);
tok = strtok_r(buf, delims, &context);
if (!tok) {
return;
}
do {
__android_log_write(priority, tag, tok);
} while ((tok = strtok_r(NULL, delims, &context)));
}
#ifndef ANDROID
// Implementations of the basic android logging functions for non-android platforms.
static char logTagChar(int prio) {
switch (prio) {
default:
case ANDROID_LOG_UNKNOWN:
case ANDROID_LOG_DEFAULT:
case ANDROID_LOG_SILENT:
return ' ';
case ANDROID_LOG_VERBOSE:
return 'V';
case ANDROID_LOG_DEBUG:
return 'D';
case ANDROID_LOG_INFO:
return 'I';
case ANDROID_LOG_WARN:
return 'W';
case ANDROID_LOG_ERROR:
return 'E';
case ANDROID_LOG_FATAL:
return 'F';
}
}
int __android_log_write(int prio, const char *tag, const char *text) {
return fprintf(stderr, "[%c/%.16s] %s\n", logTagChar(prio), tag, text);
}
int __android_log_print(int prio, const char *tag, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int res = fprintf(stderr, "[%c/%.16s] ", logTagChar(prio), tag);
res += vfprintf(stderr, "%s\n", ap);
va_end(ap);
return res;
}
#endif

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <fb/lyra.h>
#include <ios>
#include <memory>
#include <vector>
#include <dlfcn.h>
#include <unwind.h>
using namespace std;
namespace facebook {
namespace lyra {
namespace {
class IosFlagsSaver {
ios_base& ios_;
ios_base::fmtflags flags_;
public:
IosFlagsSaver(ios_base& ios)
: ios_(ios),
flags_(ios.flags())
{}
~IosFlagsSaver() {
ios_.flags(flags_);
}
};
struct BacktraceState {
size_t skip;
vector<InstructionPointer>& stackTrace;
};
_Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
BacktraceState* state = reinterpret_cast<BacktraceState*>(arg);
auto absoluteProgramCounter =
reinterpret_cast<InstructionPointer>(_Unwind_GetIP(context));
if (state->skip > 0) {
--state->skip;
return _URC_NO_REASON;
}
if (state->stackTrace.size() == state->stackTrace.capacity()) {
return _URC_END_OF_STACK;
}
state->stackTrace.push_back(absoluteProgramCounter);
return _URC_NO_REASON;
}
void captureBacktrace(size_t skip, vector<InstructionPointer>& stackTrace) {
// Beware of a bug on some platforms, which makes the trace loop until the
// buffer is full when it reaches a noexcept function. It seems to be fixed in
// newer versions of gcc. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56846
// TODO(t10738439): Investigate workaround for the stack trace bug
BacktraceState state = {skip, stackTrace};
_Unwind_Backtrace(unwindCallback, &state);
}
}
void getStackTrace(vector<InstructionPointer>& stackTrace, size_t skip) {
stackTrace.clear();
captureBacktrace(skip + 1, stackTrace);
}
// TODO(t10737622): Improve on-device symbolification
void getStackTraceSymbols(vector<StackTraceElement>& symbols,
const vector<InstructionPointer>& trace) {
symbols.clear();
symbols.reserve(trace.size());
for (size_t i = 0; i < trace.size(); ++i) {
Dl_info info;
if (dladdr(trace[i], &info)) {
symbols.emplace_back(trace[i], info.dli_fbase, info.dli_saddr,
info.dli_fname ? info.dli_fname : "",
info.dli_sname ? info.dli_sname : "");
}
}
}
ostream& operator<<(ostream& out, const StackTraceElement& elm) {
IosFlagsSaver flags{out};
// TODO(t10748683): Add build id to the output
out << "{dso=" << elm.libraryName() << " offset=" << hex
<< showbase << elm.libraryOffset();
if (!elm.functionName().empty()) {
out << " func=" << elm.functionName() << "()+" << elm.functionOffset();
}
out << " build-id=" << hex << setw(8) << 0
<< "}";
return out;
}
// TODO(t10737667): The implement a tool that parse the stack trace and
// symbolicate it
ostream& operator<<(ostream& out, const vector<StackTraceElement>& trace) {
IosFlagsSaver flags{out};
auto i = 0;
out << "Backtrace:\n";
for (auto& elm : trace) {
out << " #" << dec << setfill('0') << setw(2) << i++ << " " << elm << '\n';
}
return out;
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include <jni.h>
#ifndef DISABLE_CPUCAP
#include <fb/CpuCapabilities.h>
#endif
#include <fb/fbjni.h>
using namespace facebook::jni;
void initialize_xplatinit();
void initialize_fbjni();
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return facebook::jni::initialize(vm, [] {
initialize_fbjni();
#ifndef DISABLE_XPLAT
initialize_xplatinit();
#endif
#ifndef DISABLE_CPUCAP
initialize_cpucapabilities();
#endif
});
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* A Java Object that has native memory allocated corresponding to this instance.
*
* <p>NB: THREAD SAFETY (this comment also exists at Countable.cpp)
*
* <p>{@link #dispose} deletes the corresponding native object on whatever thread the method is
* called on. In the common case when this is called by Countable#finalize(), this will be called on
* the system finalizer thread. If you manually call dispose on the Java object, the native object
* will be deleted synchronously on that thread.
*/
@DoNotStrip
public class Countable {
// Private C++ instance
@DoNotStrip private long mInstance = 0;
public native void dispose();
protected void finalize() throws Throwable {
dispose();
super.finalize();
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class CppException extends RuntimeException {
@DoNotStrip
public CppException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class CppSystemErrorException extends CppException {
int errorCode;
@DoNotStrip
public CppSystemErrorException(String message, int errorCode) {
super(message);
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* This object holds a native C++ member for hybrid Java/C++ objects.
*
* <p>NB: THREAD SAFETY
*
* <p>{@link #dispose} deletes the corresponding native object on whatever thread the method is
* called on. In the common case when this is called by HybridData#finalize(), this will be called
* on the system finalizer thread. If you manually call resetNative() on the Java object, the C++
* object will be deleted synchronously on that thread.
*/
@DoNotStrip
public class HybridData {
// Private C++ instance
@DoNotStrip private long mNativePointer = 0;
/**
* To explicitly delete the instance, call resetNative(). If the C++ instance is referenced after
* this is called, a NullPointerException will be thrown. resetNative() may be called multiple
* times safely. Because {@link #finalize} calls resetNative, the instance will not leak if this
* is not called, but timing of deletion and the thread the C++ dtor is called on will be at the
* whim of the Java GC. If you want to control the thread and timing of the destructor, you should
* call resetNative() explicitly.
*/
public native void resetNative();
protected void finalize() throws Throwable {
resetNative();
super.finalize();
}
public boolean isValid() {
return mNativePointer != 0;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
import java.util.Iterator;
import javax.annotation.Nullable;
/**
* To iterate over an Iterator from C++ requires two calls per entry: hasNext() and next(). This
* helper reduces it to one call and one field get per entry. It does not use a generic argument,
* since in C++, the types will be erased, anyway. This is *not* a {@link java.util.Iterator}.
*/
@DoNotStrip
public class IteratorHelper {
private final Iterator mIterator;
// This is private, but accessed via JNI.
@DoNotStrip private @Nullable Object mElement;
@DoNotStrip
public IteratorHelper(Iterator iterator) {
mIterator = iterator;
}
@DoNotStrip
public IteratorHelper(Iterable iterable) {
mIterator = iterable.iterator();
}
/**
* Moves the helper to the next entry in the map, if any. Returns true iff there is an entry to
* read.
*/
@DoNotStrip
boolean hasNext() {
if (mIterator.hasNext()) {
mElement = mIterator.next();
return true;
} else {
mElement = null;
return false;
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.Nullable;
/**
* To iterate over a Map from C++ requires four calls per entry: hasNext(), next(), getKey(),
* getValue(). This helper reduces it to one call and two field gets per entry. It does not use a
* generic argument, since in C++, the types will be erased, anyway. This is *not* a {@link
* java.util.Iterator}.
*/
@DoNotStrip
public class MapIteratorHelper {
@DoNotStrip private final Iterator<Map.Entry> mIterator;
@DoNotStrip private @Nullable Object mKey;
@DoNotStrip private @Nullable Object mValue;
@DoNotStrip
public MapIteratorHelper(Map map) {
mIterator = map.entrySet().iterator();
}
/**
* Moves the helper to the next entry in the map, if any. Returns true iff there is an entry to
* read.
*/
@DoNotStrip
boolean hasNext() {
if (mIterator.hasNext()) {
Map.Entry entry = mIterator.next();
mKey = entry.getKey();
mValue = entry.getValue();
return true;
} else {
mKey = null;
mValue = null;
return false;
}
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
/** A Runnable that has a native run implementation. */
@DoNotStrip
public class NativeRunnable implements Runnable {
private final HybridData mHybridData;
private NativeRunnable(HybridData hybridData) {
mHybridData = hybridData;
}
public native void run();
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2004-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class ThreadScopeSupport {
// This is just used for ThreadScope::withClassLoader to have a java function
// in the stack so that jni has access to the correct classloader.
@DoNotStrip
private static void runStdFunction(long ptr) {
runStdFunctionImpl(ptr);
}
private static native void runStdFunctionImpl(long ptr);
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.jni;
import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class UnknownCppException extends CppException {
@DoNotStrip
public UnknownCppException() {
super("Unknown");
}
@DoNotStrip
public UnknownCppException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,11 @@
# For common use cases for the hybrid pattern, keep symbols which may
# be referenced only from C++.
-keepclassmembers class * {
com.facebook.jni.HybridData *;
<init>(com.facebook.jni.HybridData);
}
-keepclasseswithmembers class * {
com.facebook.jni.HybridData *;
}

1
libs/folly Submodule

Submodule libs/folly added at 711b3fdc69

1
libs/jni-hack/README.md Normal file
View File

@@ -0,0 +1 @@
This buck module exists so that targets that need to be built against both 1) Android (where we can and should use the Android NDK jni headers) and 2) the host platform(generally for local unit tests) can depend on a single target and get the right jni header for whatever platform they're building against automatically.

14
libs/jni-hack/jni.h Normal file
View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#pragma once
#ifdef __ANDROID__
#include_next <jni.h>
#else
#include "real/jni.h"
#endif

1132
libs/jni-hack/real/jni.h Normal file

File diff suppressed because it is too large Load Diff