Stability Coding Standards

Huawei's official stability coding standards cover NDK development, ArkTS coding, Node-API development, C++ coding, libuv usage and examples, and error-prone API usage.

https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard

This document focuses on RNOH / React Native scenarios, combining historical stability fixes, framework code patterns and stability documents, to supplement and refine RN framework-side coding standards.

Based on historical issues, high-frequency stability problems in the framework mainly concentrate in the following directions:

  • Callbacks continue executing after lifecycle ends, causing already destroyed objects but still executing callbacks.
  • Multi-thread shared state access lacks synchronization protection, causing deadlock, freeze or low-probability crash.
  • Initialization and destruction phase state boundaries are unclear, causing null pointer, UAF or assertion failure.
  • JS, ArkTS and C++ cross-layer calls lack null value protection, exception boundaries and thread constraints.
  • Platform compatibility, obfuscation and module export handling is improper, causing crash in release package or specific system versions.

Therefore, when coding, stability standards should be established around five dimensions: memory and object safety, lifecycle and destruction cleanup, concurrency and threads, exception and boundary handling, platform and module compatibility.

1. Memory and Object Safety

1.1 Asynchronous Callbacks Must Not Hold Object Strong References

Asynchronous callbacks, including VSync, Display, Timer, HTTP, NAPI etc., must not directly capture this or hold host object's strong reference. Callback and object lifecycle must be decoupled. Recommend uniformly using weak_ptr weak reference, lock and check null first when callback executes, if object already destroyed then immediately return.

If callback only needs to read small amount of read-only data, e.g., URI, path, config snapshot, event parameters etc., should prioritize copying by value into callback closure, rather than continuing to reference host object internal members. For logic that must execute on object instance, uniformly use weak_ptr weak reference and check null at callback entry.

Example:

// Correct
auto weakSelf = weak_from_this();
scheduleCallback([weakSelf]() {
    auto self = weakSelf.lock();
    if (!self) return;
    self->doWork();
});

// Forbidden
scheduleCallback([this]() {
    this->doWork();
});

If asynchronous task executes when object already destroyed, but callback still holds this, will form typical UAF or dangling reference access. Historically, AnimatedTurboModule, ImageComponentInstance, EventBeat, UI tick etc. paths all had such issues.

Historical Cases:

1.2 Shared Resource Multi-thread Access Must Be Locked

All shared containers, caches and state fields that will be concurrently accessed by multiple threads must have synchronization protection. Only protecting write not protecting read, or only locking in part of call chain, will leave data race window.

Typical high-risk objects include:

  • inflightAnimations_ type global animation state.
  • TextMeasureRegistry, FontRegistry type cross-thread read-write registry.
  • listeners, callbacks, registry, cache type shared collections.

When object may simultaneously be accessed by UI thread, JS thread, main thread or Worker thread, must clarify access model from design, cannot rely on "actual runtime probably won't conflict" assumption.

Example:

// Correct
std::lock_guard<std::mutex> lock(mutex_);
listeners_.push_back(listener);

// Wrong
listeners_.push_back(listener);

Historical Cases:

1.3 Factory Methods and Create Interface Return Values Must Be Checked

All factory methods, createNode(), lookup, find, getDescriptor(), optional style return values, must have validity check before use. Cannot assume return value always exists, cannot continue executing subsequent logic after receiving null object.

Typical risks include:

  • NodeApi or ArkUINodeContext not ready when createNode() returns null.
  • Registry query not finding descriptor but continuing execution.
  • Lookup returning null or optional being empty but directly dereferencing.

Fixing such problems' key is not adding if at crash point, but uniformly establishing failure branch and fallback logic at object creation and dependency acquisition entry.

Example:

// Correct
auto node = createNode();
if (!node) {
  return nullptr;
}
node->mount();

// Wrong
auto node = createNode();
node->mount();

Historical Cases:

1.4 Pointer, Member State and Context Object Must Be Null-checked Before Use

The following objects repeatedly appeared null pointer risk in historical problems, must explicitly validate before use:

  • Bare pointers.
  • Objects obtained from weak_ptr::lock().
  • Component members like m_eventEmitter, m_props, m_state, m_surface, m_scheduler.
  • Cross-layer returned optional objects, callback parameters and state context.

Especially in lifecycle switch, asynchronous callback late arrival, page destruction event continuing trigger, window change etc. scenarios, objects that were non-null under normal path can easily become null in boundary cases.

Example:

// Correct
if (m_eventEmitter != nullptr) {
  m_eventEmitter->dispatchEvent(event);
}

// Wrong
m_eventEmitter->dispatchEvent(event);

Historical Cases:

1.5 Avoid shared_ptr Circular Reference

If object A holds object B's shared_ptr, and object B reverse holds object A's shared_ptr, will form circular reference, causing object long-term unable to release, eventually triggering memory abnormality or resource leak. At this time should change one side to weak_ptr.

For bidirectional reference between listener, callback owner, context object and manager, code review must focus on checking whether such problem exists.

Example:

// Correct
class Child {
  std::weak_ptr<Owner> owner_;
};

// Wrong
class Child {
  std::shared_ptr<Owner> owner_;
};

Historical Cases:

2. Lifecycle and Destruction Cleanup

2.1 Destructor or destroy() Must Stop All Async Sources

After object enters destruction phase, must proactively stop all external callback sources that may continue driving this object, cannot only rely on object being released. Sources needing focus check include:

  • VSync or Display callback unregister.
  • Timer cancel, e.g., cancelTimer, stopTimer.
  • EventBeat, animation frame driver, onUITick etc. unbind.
  • HTTP request abort or completion callback cleanup.
  • Observer, listener, event emitter deregistration.

In principle, whoever registers, whoever releases; at which layer registered, must explicitly cleanup at same lifecycle responsibility boundary.

Example:

// Correct
void destroy() {
  stopTimer(timerId_);
  unsubscribeVSync();
}

// Wrong
void destroy() {
  released_ = true;
}

Historical Cases:

2.2 Destruction Order Must Be Consistent with Dependency Chain

Destruction phase cannot only focus on "whether object destructs", must also guarantee dependency chain order correct. General principle is:

  • First stop users of dependency upstream objects.
  • Then destroy depended resources.
  • Finally cleanup manager and global state.

For example, when surface stops, must first guarantee Scheduler, UIManager, LayoutAnimation etc. access paths to surface have stopped, then do unregisterSurface and resource release. Otherwise will appear lower layer object already destroyed, but upper layer still continuing calling stopSurface, updateConstraints or animation driver situation.

Example:

// Correct
stopSurface();
unregisterSurface();
releaseResources();

// Wrong
releaseResources();
stopSurface();

Historical Cases:

2.3 Initialization Path Must Guarantee Dependencies Ready

Any object depending on external context, before entering creation or access path, must confirm dependency already ready. Common requirements include:

  • Confirm ArkUINodeContext and NodeApi initialized before ArkUINode creation.
  • Confirm window state stable before window property read.
  • Confirm platform capability available before bundle loading, Ability related interface call.

Initialization phase problem common manifestation is: null pointer, assertion abort, undefined error or platform capability load failure. Coding should put ready check at entry, not remedy after call failure.

Example:

// Correct
if (!context_ || !context_->nodeApi) {
  return;
}
createNode();

// Wrong
createNode();

Historical Cases:

2.4 State Machine Migration Must Maintain Order Consistency

Component internal state change must not cross-trigger in multiple event callbacks, especially in high-frequency scenarios like scrolling, dragging, animation end, page exit and layout submit. Must guarantee:

  • State switch path unique and order stable.
  • Callback trigger won't re-enter changing current state.
  • External event notification happens after internal state complete update, or through explicit design guarantee order consistency.

If state migration and callback cross execute, easily cause state machine entering illegal branch, eventually crashing at next access.

Example:

// Correct
state_ = State::Idle;
emitScrollStop();

// Wrong
emitScrollStop();
state_ = State::Idle;

Historical Cases:

2.5 Registration and Unregistration Must Form Closed Loop

All registration behaviors must have symmetric unregistration behavior, and unregistration must happen at object destruction, cannot rely on GC or host environment automatic回收. Interfaces needing pair check include:

  • addListener / removeListener.
  • subscribe / unsubscribe.
  • addEventListener / removeEventListener.
  • napi_create_reference / napi_delete_reference.
  • Request initiation / callback context release after request completion.

Where NAPI reference, NAPI value and associated handle creation, access and release, must strictly follow所属 env and thread context constraints. Cannot pass reference or handle across thread then release in wrong thread, also cannot treat "currently in JS thread" as sufficient condition to arbitrarily recycle NAPI resources.

If registration and unregistration don't form closed loop, short-term may just be object not released, long-term will evolve into thread leak, handle leak, memory膨胀 or callback continue executing after instance destruction.

Example:

// Correct
listenerId_ = addListener(callback);
removeListener(listenerId_);

// Wrong
addListener(callback);

Historical Cases:

3. Concurrency and Threads

3.1 Multiple Locks Acquisition Order Must Be Globally Consistent

As long as某个模块 will simultaneously hold two or more locks, must prescribe globally consistent locking order. Different threads if using different order acquiring same group of locks, will form circular wait, causing deadlock.

Recommended practice:

  • Use std::scoped_lock or std::lock atomic acquire multiple locks.
  • Explicit lock order constraint in comments or module design.
  • Avoid cross-function implicit持锁, prevent caller and callee叠加 forming order confusion.

Example:

// Recommended
std::scoped_lock lock(mutexA, mutexB);

// Wrong
std::lock_guard<std::mutex> lockA(mutexA);
std::lock_guard<std::mutex> lockB(mutexB);

FontRegistry, TextMeasureRegistry, animation registry etc. are all historically high-risk areas where lock order problems occurred.

Historical Cases:

3.2 Must Not Initiate Cross-thread Sync Wait While Holding Lock

In state of holding lock, must not:

  • Call waitForSyncTask type sync wait interface.
  • Post task to other thread then block wait for result.
  • Directly call user callback or framework callback that may re-enter current lock域.

Correct practice is:

  • First copy necessary data.
  • Release lock.
  • Then execute cross-thread dispatch or callback.

reportMount, scheduler callback, page lifecycle switch etc. paths especially should avoid "hold lock dispatch + wait return" style.

Example:

// Correct
{
  std::lock_guard<std::mutex> lock(mutex_);
  pending_ = data;
}
runOnMainThread(task);

// Wrong
std::lock_guard<std::mutex> lock(mutex_);
waitForSyncTask(task);

Historical Cases:

3.3 Each Type Operation Must Execute on Prescribed Thread

Different objects and system interfaces have explicit thread归属, coding cannot only看 code can call, must also guarantee call thread legal.

Common requirements as below:

  • ArkUI node operations must be on UI thread.
  • ArkUI's markDirty type node dirty mark operations must be placed on main thread/UI thread execute, cannot directly call on background thread.
  • NAPI calls must be on JS thread.
  • Window related property read must execute in window allowed thread context.
  • Platform interfaces like getSDKApiVersion type capability call must符合其 thread限制.

If某个逻辑 runs on Worker thread, but needs access main thread object, must explicitly switch to main thread queue, cannot directly cross-thread access.

Example:

// Correct
runOnMainThread([this]() { updateWindow(); });

// Wrong
updateWindow();

Historical Cases:

3.4 Shared State Getter Also Needs Thread Safety Consideration

Thread safety problem not only appears in write path, read path also同样可能有风险. Especially getter if internally reads mutable shared state, must guarantee:

  • Read path and write path use same set of synchronization protection.
  • Not return already invalid reference, pointer or context.
  • Not expose temporary state on current thread stack to async callback or other thread.

Historically HostObjectProxy, JSVMRuntime etc. paths once had intermittent crash due to getter cross-thread shared state.

Example:

// Correct
std::string getName() {
  std::lock_guard<std::mutex> lock(mutex_);
  return name_;
}

// Wrong
const std::string& getName() {
  return name_;
}

Historical Cases:

3.5 Transaction Processing Must Guarantee Data Source Consistent and Order Stable

In performTransaction, mutation分桶, slice parallel processing etc. high-frequency transaction链路, must ensure:

  • Front and back phases use same transaction data.
  • Won't disorder Create, Insert, Delete etc. mutation order due to parallel slice or async dispatch.
  • All order-dependent operations retain order constraint when拆分.

If transaction链路 reads inconsistent data副本 at different phases, may cause mount order disorder, state inconsistency, eventually evolving into crash or low-probability异常.

Example:

// Correct
auto &mutations = transaction.getMutations();
splitAndDispatch(mutations);

// Wrong
splitAndDispatch(snapshotA);
applyLater(snapshotB);

Historical Cases:

4. Exception and Boundary Handling

4.1 noexcept Only for Functions That Truly Won't Throw Exception

noexcept is not performance optimization label, but exception boundary承诺. Any function marked noexcept must guarantee its internal call chain won't have exception穿透出去. Following scenarios cannot arbitrarily add noexcept:

  • Destructor contains complex logic, callback, resource release or cross-layer call.
  • Virtual function override calls third-party library or custom logic.
  • System callback interface may still throw runtime_error, logic_error etc. exceptions.

If function internal may throw exception, should take one of以下方式:

  • Remove unreasonable noexcept.
  • try-catch at boundary and convert to recoverable error.
  • Convert error to log, status code or explicit failure branch.

Otherwise once exception穿透 noexcept boundary, will directly trigger terminate, eventually表现为 SIGABRT.

Example:

// Correct
void flush() {
  mayThrow();
}

// Wrong
void flush() noexcept {
  mayThrow();
}

Historical Cases:

4.2 Non-void Function All Branches Must Return

Function declared return value, must guarantee all control flow branches have explicit return, cannot rely on undefined behavior. Especially following几类 functions:

  • Chain interfaces returning object self reference.
  • Tool functions returning status enum, boolean or pointer.
  • State transition functions in switch or multi-layer condition judgment.

If遗漏某个 branch's return value, caller continuing use result may fall into undefined state,表现为 SIGTRAP, SIGSEGV or subsequent harder to track logic error.

Example:

// Correct
int getWidth(bool ready) {
  if (!ready) return 0;
  return width_;
}

// Wrong
int getWidth(bool ready) {
  if (ready) return width_;
}

Historical Cases:

4.3 JS and ArkTS Layers Must Have Comprehensive Null Value Protection

JS and ArkTS layers have low tolerance for undefined, null, unhandled exception will directly evolve into JS Crash. Following scenarios must have null value protection:

  • Access object property in async callback.
  • Access socket, window, context etc. objects in close, destroy, reset paths.
  • Read optional parameter, cross-layer return value and platform API call result.

Recommended pattern:

if (this.socket == undefined) {
  return;
}
this.socket.close();

Or use optional chain, default value and explicit degradation branch, rather than assuming object always exists.

Example:

// Correct
if (socket != undefined) {
  socket.close();
}

// Wrong
socket.close();

Historical Cases:

4.4 Boundary Input Must Have Legality Check at Entry

All external input, including props, module parameters, path, config, event data, must validate type and legality at entry. Following情况 cannot directly pass down:

  • Illegal keyboardType, enum value or status value.
  • Directory, file, handle type混用.
  • Not providing cache key, descriptor, component name etc. key fields.
  • Convention structure incomplete object.

Correct practice is fail early, give log and enter recoverable branch, rather than waiting底层代码 crash in unknown state.

Example:

// Correct
if (!isValidKeyboardType(type)) {
  return "default";
}

// Wrong
return type;

Historical Cases:

4.5 Container and String Access Must Check Boundary

Access to vector, array, map, string etc. containers, must confirm index, position and value exist, cannot assume collection state always consistent with previous step. Especially in concurrent scenario or state switch scenario, size read previous line, next line未必仍然 valid.

Typical problems needing focus check include:

  • Continue accessing by old index after deleting all items.
  • String pos out of bounds.
  • Text layout result position calculation error.
  • Dynamic object unpacking not checking whether field exists.

Example:

// Correct
if (index < items_.size()) {
  return items_[index];
}

// Wrong
return items_[index];

Historical Cases:

4.6 Sort Comparator Must Satisfy Strict Weak Ordering, Use stable_sort When Needing Stable Result

When using std::sort in C++, comparator must satisfy strict weak ordering requirement, cannot write "equal elements" also as可互相判定为更小. For comparator not satisfying strict weak ordering, std::sort behavior属于 undefined behavior, cannot rely on "small data量看起来正常" or "部分输入下没有复现问题" phenomenon判断 implementation correct. Once comparator returns wrong result for equal elements, sort process may出现 wrong result, abnormal access or crash.

If elements之间可能 equal, and business上需要保持 equal elements' original relative order, should use std::stable_sort; if不需要 stability, also仍然必须保证 std::sort used comparator satisfies strict weak ordering, cannot write <=, >= or other会破坏比较关系 form.

Example:

// Correct
std::stable_sort(items.begin(), items.end(), [](const Item& left, const Item& right) {
  return left.priority < right.priority;
});

// Wrong
std::sort(items.begin(), items.end(), [](const Item& left, const Item& right) {
  return left.priority <= right.priority;
});

Historical Cases:

5. Platform and Module Compatibility

5.1 ArkTS Prohibits Manual declare System Functions

In ArkTS file, should not through declare function way manually declare system functions, e.g., px2vp. Because这类 declaration in release混淆后可能无法按原始符号名解析, eventually引发 undefined error or startup crash.

Correct practice is use platform formally provided capability entry or import方式, not self-construct runtime parse dependency.

Example:

// Correct
import { LengthMetrics } from "@kit.ArkUI";

// Wrong
declare function px2vp(value: number): number;

Historical Cases:

5.2 ArkTS Private Fields Prohibit Using Double Underscore Prefix

In release build or obfuscation scenario, fields and method names starting with double underscore可能被重命名, causing runtime cannot find corresponding property or method. Therefore:

  • Don't use __xxx as private field and method naming.
  • Prioritize using _xxx or normal semantic naming.
  • For members needing stable expose to runtime, reflection or export layer,更应该避免 obfuscation敏感命名.

Example:

// Correct
private _windowSize = 0

// Wrong
private __windowSize = 0

Historical Cases:

5.3 Platform API Must Have Version Compatibility Check

System API doesn't guarantee available under all API Level. Coding must:

  • First check platform version.
  • Then decide whether call specific interface.
  • Provide degradation implementation or skip path for unsupported platform.

Especially API12 related capabilities, libability_runtime.so load链路, page registration capability etc., historically all出现过 platform compatibility missing causing crash problem.

Example:

// Correct
if (apiVersion >= 12) {
  useNewApi();
}

// Wrong
useNewApi();

Historical Cases:

5.4 Custom Component Naming Must Not Conflict with Framework Reserved Prefix

Component registration, descriptor lookup, module export etc. paths, all依赖 stable naming convention. Custom components and modules must not use framework reserved prefix, e.g., RCT, otherwise可能 at runtime conflict with framework built-in objects, causing registration failure or component creation crash.

Example:

// Correct
export const FancyButton = ...

// Wrong
export const RCTFancyButton = ...

Historical Cases:

5.5 Export Symbols and Module Implementation Must Avoid Conflict with Third-party Libraries

NativeNodeApi, NAPI export objects, dynamic link symbols etc., all可能 because与三方库同名而被覆盖. Coding should avoid使用过于泛化 export名, and at module boundary明确 namespace and export策略, prevent runtime symbol parse error.

Example:

// Correct
namespace rnoh {
class HarmonyNodeApi {};
}

// Wrong
class NativeNodeApi {};

Historical Cases:

6. Stability Self-check Checklist in Code Review

When submitting code or CR, suggest对照以下问题快速自查:

6.1 Lifecycle Check

  • Every async callback, whether will still execute after object destruction.
  • Whether destroy() or destructor stopped all VSync, Timer, HTTP, listener and observer.
  • Whether存在 destructor过程中继续触发 external callback path.

6.2 Thread and Lock Check

  • Whether multiple locks' locking order globally consistent.
  • Whether done cross-thread sync wait while holding lock.
  • Whether ArkUI, NAPI, window property etc. operations running on correct thread.
  • Whether getter or read path遗漏 thread sync protection.

6.3 Null Value and Boundary Check

  • Whether pointer, optional, registry query result all done null check.
  • Whether container, string and text position access checked boundary.
  • Whether sort comparator satisfies strict weak ordering; when elements可能 equal and need maintain order whether use stable_sort.
  • Whether illegal props, config, parameter value intercepted at entry.

6.4 Exception Boundary Check

  • Whether noexcept只用于真正不会抛异常的函数.
  • Whether all non-void functions return in all branches.
  • Whether async object access in JS and ArkTS done undefined or null protection.

6.5 Compatibility and Resource Release Check

  • Whether called有 platform version限制 interface, but没有做 compatibility判断.
  • Whether ArkTS中手工 declare了 system function.
  • Whether使用了 double underscore prefix敏感命名.
  • Whether each listener, reference, request, thread, cache都有 symmetric release path.