Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/react-native-codegen/src/generators/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ function toSafeCppString(input: string): string {
return input.split('-').map(toPascalCase).join('');
}

function toCppString(s: string): string {
return JSON.stringify(s);
}

function toJavaString(s: string): string {
return JSON.stringify(s);
}

function toObjCString(s: string): string {
return '@' + JSON.stringify(s);
}

function getEnumName(moduleName: string, origEnumName: string): string {
const uppercasedPropName = toSafeCppString(origEnumName);
return `${moduleName}${uppercasedPropName}`;
Expand Down Expand Up @@ -104,6 +116,9 @@ module.exports = {
capitalize,
indent,
parseValidUnionType,
toCppString,
toJavaString,
toObjCString,
toPascalCase,
toSafeCppString,
getEnumName,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

'use strict';

const {toCppString, toJavaString, toObjCString} = require('../Utils');

describe('toCppString', () => {
it('wraps string in quotes', () => {
expect(toCppString('ModuleName')).toBe('"ModuleName"');
});

it('escapes backslashes and double quotes', () => {
expect(toCppString('path\\to\\"file"')).toBe('"path\\\\to\\\\\\"file\\""');
});

it('escapes control characters', () => {
expect(toCppString('a\tb\nc')).toBe('"a\\tb\\nc"');
});

it('preserves Unicode characters', () => {
expect(toCppString('café😀日本語')).toBe('"café😀日本語"');
});

it('escapes C++ code injection attempts', () => {
expect(toCppString('Name"; std::cout << "test"; //')).toBe(
'"Name\\"; std::cout << \\"test\\"; //"',
);
});

it('handles empty string', () => {
expect(toCppString('')).toBe('""');
});
});

describe('toJavaString', () => {
it('wraps string in quotes', () => {
expect(toJavaString('ModuleName')).toBe('"ModuleName"');
});

it('escapes Java code injection attempts', () => {
expect(
toJavaString('Name"; } static { System.out.println("test"); } //'),
).toBe('"Name\\"; } static { System.out.println(\\"test\\"); } //"');
});

it('preserves Unicode characters', () => {
expect(toJavaString('café😀')).toBe('"café😀"');
});
});

describe('toObjCString', () => {
it('wraps string in @"" quotes', () => {
expect(toObjCString('ModuleName')).toBe('@"ModuleName"');
});

it('escapes Obj-C code injection attempts', () => {
expect(toObjCString('Name"; NSLog(@"test"); //')).toBe(
'@"Name\\"; NSLog(@\\"test\\"); //"',
);
});

it('preserves Unicode characters', () => {
expect(toObjCString('日本語')).toBe('@"日本語"');
});

it('handles empty string', () => {
expect(toObjCString('')).toBe('@""');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type {
SchemaType,
} from '../../CodegenSchema';

const {toObjCString} = require('../Utils');

type FilesOutput = Map<string, string>;

function getOrdinalNumber(num: number): string {
Expand Down Expand Up @@ -68,7 +70,7 @@ const CommandHandlerIfCaseConvertArgTemplate = ({
`
NSObject *arg${argNumber} = args[${argNumber}];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, @"${expectedKindString}", @"${componentName}", commandName, @"${argNumberString}")) {
if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, ${toObjCString(expectedKindString)}, ${toObjCString(componentName)}, commandName, ${toObjCString(argNumberString)})) {
return;
}
#endif
Expand All @@ -89,10 +91,10 @@ const CommandHandlerIfCaseTemplate = ({
commandCall: string,
}) =>
`
if ([commandName isEqualToString:@"${commandName}"]) {
if ([commandName isEqualToString:${toObjCString(commandName)}]) {
#if RCT_DEBUG
if ([args count] != ${numArgs}) {
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"${componentName}", commandName, (int)[args count], ${numArgs});
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", ${toObjCString(componentName)}, commandName, (int)[args count], ${numArgs});
return;
}
#endif
Expand Down Expand Up @@ -120,7 +122,7 @@ RCT_EXTERN inline void RCT${componentName}HandleCommand(
${ifCases}

#if RCT_DEBUG
RCTLogError(@"%@ received command %@, which is not a supported command.", @"${componentName}", commandName);
RCTLogError(@"%@ received command %@, which is not a supported command.", ${toObjCString(componentName)}, commandName);
#endif
}
`.trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import type {
SchemaType,
} from '../../CodegenSchema';

const {indent} = require('../Utils');
const {parseValidUnionType} = require('../Utils');
const {indent, parseValidUnionType, toCppString} = require('../Utils');
const {IncludeTemplate, generateEventStructName} = require('./CppHelpers');

// File path -> contents
Expand Down Expand Up @@ -74,7 +73,7 @@ const ComponentTemplate = ({
: '';
return `
void ${className}EventEmitter::${eventName}(${structName} event) const {
dispatchEvent("${dispatchEventName}", [${capture}](jsi::Runtime &runtime) {
dispatchEvent(${toCppString(dispatchEventName)}, [${capture}](jsi::Runtime &runtime) {
${implementation}
});
}
Expand All @@ -92,7 +91,7 @@ const BasicComponentTemplate = ({
}) =>
`
void ${className}EventEmitter::${eventName}() const {
dispatchEvent("${dispatchEventName}");
dispatchEvent(${toCppString(dispatchEventName)});
}
`.trim();

Expand All @@ -106,7 +105,7 @@ function generateSetter(
const eventChain = usingEvent
? `event.${[...propertyParts, propertyName].join('.')}`
: [...propertyParts, propertyName].join('.');
return `${variableName}.setProperty(runtime, "${propertyName}", ${valueMapper(
return `${variableName}.setProperty(runtime, ${toCppString(propertyName)}, ${valueMapper(
eventChain,
)});`;
}
Expand All @@ -132,7 +131,7 @@ function generateObjectSetter(
),
2,
)}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
${variableName}.setProperty(runtime, ${toCppString(propertyName)}, ${propertyName});
}
`.trim();
}
Expand Down Expand Up @@ -175,7 +174,7 @@ function generateArraySetter(
usingEvent,
)}
}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
${variableName}.setProperty(runtime, ${toCppString(propertyName)}, ${propertyName});
`;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const {wrapOptional} = require('../TypeUtils/Cxx');
const {
getEnumName,
parseValidUnionType,
toCppString,
toPascalCase,
toSafeCppString,
} = require('../Utils');
Expand Down Expand Up @@ -176,13 +177,13 @@ const ModuleSpecClassDeclarationTemplate = ({
template <typename T>
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
public:
static constexpr std::string_view kModuleName = "${moduleName}";
static constexpr std::string_view kModuleName = ${toCppString(moduleName)};

protected:
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{${hasteModuleName}CxxSpec::kModuleName}, jsInvoker) {
${methods
.map(({methodName, paramCount}) => {
return ` methodMap_["${methodName}"] = MethodMetadata {.argCount = ${paramCount}, .invoker = __${methodName}};`;
return ` methodMap_[${toCppString(methodName)}] = MethodMetadata {.argCount = ${paramCount}, .invoker = __${methodName}};`;
})
.join(
'\n',
Expand Down Expand Up @@ -405,9 +406,9 @@ struct ${structName}Bridging {
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` value.hasProperty(rt, "${v.name}") ? std::make_unique<T>(bridging::fromJs<T>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)) : nullptr`;
return ` value.hasProperty(rt, ${toCppString(v.name)}) ? std::make_unique<T>(bridging::fromJs<T>(rt, value.getProperty(rt, ${toCppString(v.name)}), jsInvoker)) : nullptr`;
} else {
return ` bridging::fromJs<decltype(types.${v.name})>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`;
return ` bridging::fromJs<decltype(types.${v.name})>(rt, value.getProperty(rt, ${toCppString(v.name)}), jsInvoker)`;
}
})
.join(',\n')}};
Expand All @@ -427,14 +428,14 @@ ${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, *value.${v.name}, jsInvoker));
result.setProperty(rt, ${toCppString(v.name)}, bridging::toJs(rt, *value.${v.name}, jsInvoker));
}`;
} else if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
result.setProperty(rt, ${toCppString(v.name)}, bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
return ` result.setProperty(rt, ${toCppString(v.name)}, bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
Expand Down Expand Up @@ -495,7 +496,7 @@ struct Bridging<${enumName}> {

function getMemberValueAppearance(member: NativeModuleEnumMember['value']) {
if (member.type === 'StringLiteralTypeAnnotation') {
return `"${member.value}"`;
return toCppString(member.value);
} else {
return member.value;
}
Expand Down Expand Up @@ -644,9 +645,9 @@ function translateEventEmitterToCpp(
return {
isVoidTypeAnnotation: isVoidTypeAnnotation,
templateName: isVoidTypeAnnotation ? `/*${templateName}*/` : templateName,
registerEventEmitter: ` eventEmitterMap_["${
eventEmitter.name
}"] = std::make_shared<AsyncEventEmitter<${
registerEventEmitter: ` eventEmitterMap_[${toCppString(
eventEmitter.name,
)}] = std::make_shared<AsyncEventEmitter<${
isVoidTypeAnnotation ? '' : 'jsi::Value'
}>>();`,
emitFunction: `
Expand All @@ -666,7 +667,7 @@ function translateEventEmitterToCpp(
}
static_cast<AsyncEventEmitter<${
isVoidTypeAnnotation ? '' : 'jsi::Value'
}>&>(*eventEmitterMap_["${eventEmitter.name}"]).emit(${
}>&>(*eventEmitterMap_[${toCppString(eventEmitter.name)}]).emit(${
isVoidTypeAnnotation
? ''
: `[jsInvoker = jsInvoker_, eventValue = value](jsi::Runtime& rt) -> jsi::Value {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {AliasResolver} from './Utils';

const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Java');
const {parseValidUnionType, toPascalCase} = require('../Utils');
const {parseValidUnionType, toJavaString, toPascalCase} = require('../Utils');
const {createAliasResolver, getModules} = require('./Utils');

type FilesOutput = Map<string, string>;
Expand Down Expand Up @@ -58,7 +58,7 @@ package ${packageName};
${imports}

public abstract class ${className} extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "${jsName}";
public static final String NAME = ${toJavaString(jsName)};

public ${className}(ReactApplicationContext reactContext) {
super(reactContext);
Expand All @@ -83,7 +83,7 @@ function EventEmitterTemplate(
? `${translateEventEmitterTypeToJavaType(eventEmitter, imports)} value`
: ''
}) {
mEventEmitterCallback.invoke("${eventEmitter.name}"${
mEventEmitterCallback.invoke(${toJavaString(eventEmitter.name)}${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? ', value'
: ''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {
import type {AliasResolver} from './Utils';

const {unwrapNullable} = require('../../parsers/parsers-commons');
const {parseValidUnionType} = require('../Utils');
const {parseValidUnionType, toCppString} = require('../Utils');
const {createAliasResolver, getModules} = require('./Utils');

type FilesOutput = Map<string, string>;
Expand All @@ -50,7 +50,7 @@ const HostFunctionTemplate = ({
}>) => {
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, ${toCppString(propertyName)}, ${toCppString(jniSignature)}, args, count, cachedMethodId);
}`;
};

Expand All @@ -71,14 +71,14 @@ ${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const JavaTurboModule::Init
: JavaTurboModule(params) {
${methods
.map(({propertyName, argCount}) => {
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
return ` methodMap_[${toCppString(propertyName)}] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
})
.join('\n')}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
eventEmitterMap_[${toCppString(eventEmitter.name)}] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
})
.join('')
: ''
Expand All @@ -95,7 +95,7 @@ const ModuleLookupTemplate = ({
moduleName,
hasteModuleName,
}: $ReadOnly<{moduleName: string, hasteModuleName: string}>) => {
return ` if (moduleName == "${moduleName}") {
return ` if (moduleName == ${toCppString(moduleName)}) {
return std::make_shared<${hasteModuleName}SpecJSI>(params);
}`;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {capitalize, toObjCString} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');

const StructTemplate = ({
Expand Down Expand Up @@ -292,7 +292,7 @@ function serializeConstantsStruct(
varDecl += '.get()';
}

const assignment = `d[@"${propName}"] = ` + objCValue;
const assignment = `d[${toObjCString(propName)}] = ` + objCValue;
return ` ${varDecl};\n ${assignment};`;
})
.join('\n'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {capitalize, toObjCString} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');

const StructTemplate = ({
Expand Down Expand Up @@ -62,7 +62,7 @@ const MethodTemplate = ({
safePropertyName: string,
}>) => `inline ${returnType}JS::${hasteModuleName}::${structName}::${safePropertyName}() const
{
id const p = _v[@"${propertyName}"];
id const p = _v[${toObjCString(propertyName)}];
return ${returnValue};
}`;

Expand Down
Loading
Loading