pebble/src/fw/applib/rockyjs/rocky.c
2025-01-27 11:38:16 -08:00

158 lines
5.1 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "rocky.h"
#include "jmem-heap.h"
#include "applib/rockyjs/api/rocky_api.h"
#include "applib/rockyjs/api/rocky_api_util.h"
#include "applib/rockyjs/pbl_jerry_port.h"
#include "applib/app.h"
#include "applib/applib_resource_private.h"
#include "applib/app_heap_analytics.h"
#include "applib/app_heap_util.h"
#include "kernel/pbl_malloc.h"
#include "process_management/app_manager.h"
#include "system/passert.h"
#include "syscall/syscall.h"
#include "syscall/syscall_internal.h"
#include "code_space_reservation.h"
const RockySnapshotHeader ROCKY_EXPECTED_SNAPSHOT_HEADER = {
.signature = {'P', 'J', 'S', 0}, // C-string terminator in case somebody treats this as source
#if CAPABILITY_HAS_JAVASCRIPT
.version = (uint8_t)CAPABILITY_JAVASCRIPT_BYTECODE_VERSION,
#endif
};
static void prv_rocky_init(void) {
rocky_runtime_context_init();
jerry_init(JERRY_INIT_EMPTY);
rocky_api_watchface_init();
}
bool rocky_is_snapshot(const uint8_t *buffer, size_t buffer_size) {
#if CAPABILITY_HAS_JAVASCRIPT
const size_t header_length = sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER);
if (buffer_size < header_length ||
memcmp(ROCKY_EXPECTED_SNAPSHOT_HEADER.signature,
buffer,
sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER.signature)) != 0) {
return false;
}
const uint8_t actual_version = buffer[offsetof(RockySnapshotHeader, version)];
const uint8_t expected_version = ROCKY_EXPECTED_SNAPSHOT_HEADER.version;
if (expected_version != actual_version) {
PBL_LOG(LOG_LEVEL_WARNING, "incompatible JS snapshot version %"PRIu8" (expected: %"PRIu8")",
actual_version, expected_version);
return false;
}
return jerry_is_snapshot(buffer + header_length, buffer_size - header_length);
#else
return false;
#endif
}
static bool prv_rocky_eval_buffer(const uint8_t *buffer, size_t buffer_size) {
jerry_value_t rv;
if (rocky_is_snapshot(buffer, buffer_size)) {
buffer += sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER);
buffer_size -= sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER);
PBL_ASSERTN((uintptr_t)buffer % 8 == 0);
rv = jerry_exec_snapshot(buffer, buffer_size, false);
} else {
PBL_LOG(LOG_LEVEL_INFO, "Not a snapshot, interpreting buffer as JS source code");
rv = jerry_eval((jerry_char_t *) buffer, buffer_size, false);
}
bool error_occurred = jerry_value_has_error_flag(rv);
if (error_occurred) {
jerry_value_clear_error_flag(&rv);
rocky_log_exception("Evaluating JS", rv);
}
jerry_release_value(rv);
return !(error_occurred);
}
static void prv_rocky_deinit(void) {
app_heap_analytics_log_stats_to_app_heartbeat(true /* is_rocky_app */);
rocky_api_deinit();
jerry_cleanup();
rocky_runtime_context_deinit();
}
bool rocky_event_loop_with_string_or_snapshot(const void *buffer, size_t buffer_size) {
#if CAPABILITY_HAS_JAVASCRIPT
prv_rocky_init();
const bool result = prv_rocky_eval_buffer(buffer, buffer_size);
if (result) {
app_event_loop_common();
}
prv_rocky_deinit();
return result;
#else
return false;
#endif
}
static bool prv_rocky_event_loop_with_resource(ResAppNum app_num, uint32_t resource_id) {
#if CAPABILITY_HAS_JAVASCRIPT
if (!sys_get_current_app_is_rocky_app()) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Cannot execute JavaScript, insufficient meta data.");
return false;
}
bool rv = false;
size_t sz = sys_resource_size(app_num, resource_id);
char *script = applib_resource_mmap_or_load(app_num,
resource_id,
0, sz, true);
if (script) {
// TODO: PBL-40010 clean this up
// hotfix: we're either dealing with mmap, which is 8 byte aligned already
// or malloc`ed buffer which has 7 additional bytes at the end.
// We're are moving over the bytes so that they are 8-byte aligned
// and pass that pointer to rocky instead
char *aligned_script = (char *)((uintptr_t)(script + 7) & ~7);
if (aligned_script != script) {
// don't write if it's aligned, to avoid writing to mmapped data
memmove(aligned_script, script, sz);
}
rv = rocky_event_loop_with_string_or_snapshot(aligned_script, sz);
applib_resource_munmap_or_free(script);
}
return rv;
#else
return false;
#endif
}
bool rocky_event_loop_with_system_resource(uint32_t resource_id) {
return prv_rocky_event_loop_with_resource(SYSTEM_APP, resource_id);
}
bool rocky_event_loop_with_resource(uint32_t resource_id) {
return prv_rocky_event_loop_with_resource(sys_get_current_resource_num(),
resource_id);
}