Skip to content

Commit 95fe79e

Browse files
fix(runtime): prevent crash from path substr operations
1 parent 8d2c6c6 commit 95fe79e

File tree

1 file changed

+64
-29
lines changed

1 file changed

+64
-29
lines changed

NativeScript/runtime/ModuleInternal.mm

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
#include <utime.h>
77
#include <string>
88
#include "Caches.h"
9+
#include "DevFlags.h"
910
#include "Helpers.h"
1011
#include "ModuleInternalCallbacks.h" // for ResolveModuleCallback
1112
#include "NativeScriptException.h"
12-
#include "DevFlags.h"
1313
#include "Runtime.h" // for GetAppConfigValue
1414
#include "RuntimeConfig.h"
1515

@@ -177,7 +177,8 @@ bool IsESModule(const std::string& path) {
177177
Log(@"Error loading ES module: %s", path.c_str());
178178
Log(@"Exception: %s", ex.getMessage().c_str());
179179
Log(@"***** End stack trace - continuing execution *****");
180-
Log(@"Debug mode - ES module loading failed, but telling iOS it succeeded to prevent app termination");
180+
Log(@"Debug mode - ES module loading failed, but telling iOS it succeeded to prevent app "
181+
@"termination");
181182
return true; // avoid termination in debug
182183
} else {
183184
return false;
@@ -225,11 +226,10 @@ bool IsESModule(const std::string& path) {
225226
@"app termination");
226227

227228
// Add a small delay to ensure error modal has time to render before we return
228-
dispatch_after(
229-
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)),
230-
dispatch_get_main_queue(), ^{
231-
Log(@"🛡️ Debug mode - Crash prevention complete, app should remain stable");
232-
});
229+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)),
230+
dispatch_get_main_queue(), ^{
231+
Log(@"🛡️ Debug mode - Crash prevention complete, app should remain stable");
232+
});
233233

234234
return true; // LIE TO iOS - return success to prevent app termination
235235
} else {
@@ -302,7 +302,9 @@ bool IsESModule(const std::string& path) {
302302
if (*s) {
303303
moduleName.assign(*s, s.length());
304304
if (moduleName.rfind("http://", 0) == 0 || moduleName.rfind("https://", 0) == 0) {
305-
std::string msg = std::string("NativeScript: require() of URL module is not supported: ") + moduleName + ". Use dynamic import() instead.";
305+
std::string msg =
306+
std::string("NativeScript: require() of URL module is not supported: ") + moduleName +
307+
". Use dynamic import() instead.";
306308
throw NativeScriptException(msg.c_str());
307309
}
308310
}
@@ -357,7 +359,6 @@ bool IsESModule(const std::string& path) {
357359
fullPath = [NSString stringWithUTF8String:moduleName.c_str()];
358360
}
359361

360-
361362
NSString* fileNameOnly = [fullPath lastPathComponent];
362363
NSString* pathOnly = [fullPath stringByDeletingLastPathComponent];
363364

@@ -638,7 +639,15 @@ throw NativeScriptException(isolate,
638639
stringByDeletingLastPathComponent] UTF8String];
639640

640641
// Shorten the parentDir for GetRequireFunction to avoid V8 parsing issues with long paths
641-
std::string shortParentDir = "/app" + parentDir.substr(RuntimeConfig.ApplicationPath.length());
642+
std::string shortParentDir;
643+
if (parentDir.length() >= RuntimeConfig.ApplicationPath.length() &&
644+
parentDir.compare(0, RuntimeConfig.ApplicationPath.length(), RuntimeConfig.ApplicationPath) ==
645+
0) {
646+
shortParentDir = "/app" + parentDir.substr(RuntimeConfig.ApplicationPath.length());
647+
} else {
648+
// Fallback: use the entire path if it doesn't start with ApplicationPath
649+
shortParentDir = parentDir;
650+
}
642651

643652
Local<v8::Function> require = GetRequireFunction(isolate, shortParentDir);
644653
// Use full paths for __filename and __dirname to match module.id
@@ -713,7 +722,8 @@ throw NativeScriptException(isolate,
713722
canonicalPath.c_str());
714723
return Local<Value>();
715724
} else {
716-
throw NativeScriptException(isolate, "Classic script compilation failed for " + canonicalPath);
725+
throw NativeScriptException(isolate,
726+
"Classic script compilation failed for " + canonicalPath);
717727
}
718728
}
719729

@@ -825,7 +835,8 @@ ScriptOrigin origin(isolate, urlString,
825835
tns::LogError(isolate, tc);
826836
}
827837
Log(@"***** End stack trace - continuing execution *****");
828-
Log(@"Debug mode - Script compilation failed, returning gracefully: %s", canonicalPath.c_str());
838+
Log(@"Debug mode - Script compilation failed, returning gracefully: %s",
839+
canonicalPath.c_str());
829840
// Return empty script to prevent crashes
830841
return Local<Script>();
831842
} else {
@@ -852,7 +863,8 @@ ScriptOrigin origin(isolate, urlString,
852863

853864
Local<v8::String> urlString;
854865
if (!v8::String::NewFromUtf8(isolate, url.c_str(), NewStringType::kNormal).ToLocal(&urlString)) {
855-
throw NativeScriptException(isolate, "Failed to create URL string for ES module " + canonicalPath);
866+
throw NativeScriptException(isolate,
867+
"Failed to create URL string for ES module " + canonicalPath);
856868
}
857869

858870
ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false, false,
@@ -861,12 +873,15 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
861873
ScriptCompiler::Source source(sourceText, origin, cacheData);
862874

863875
// 2) Compile with its own TryCatch
864-
// Phase diagnostics helper (local lambda) – only active in debug builds when logScriptLoading is enabled
865-
auto logPhase = [&](const char* phase, const char* status, const char* classification = "", const char* extra = "") {
876+
// Phase diagnostics helper (local lambda) – only active in debug builds when logScriptLoading is
877+
// enabled
878+
auto logPhase = [&](const char* phase, const char* status, const char* classification = "",
879+
const char* extra = "") {
866880
if (RuntimeConfig.IsDebug && IsScriptLoadingLogEnabled()) {
867881
if (classification && classification[0] != '\0') {
868882
if (extra && extra[0] != '\0') {
869-
Log(@"[esm][%s][%s][%s] %s %s", phase, status, classification, canonicalPath.c_str(), extra);
883+
Log(@"[esm][%s][%s][%s] %s %s", phase, status, classification, canonicalPath.c_str(),
884+
extra);
870885
} else {
871886
Log(@"[esm][%s][%s][%s] %s", phase, status, classification, canonicalPath.c_str());
872887
}
@@ -896,8 +911,11 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
896911
v8::String::Utf8Value w(isolate, msg->Get());
897912
if (*w) {
898913
std::string m(*w);
899-
if (m.find("Unexpected token") != std::string::npos || m.find("SyntaxError") != std::string::npos) classification = "syntax";
900-
else if (m.find("Cannot use import statement outside a module") != std::string::npos) classification = "not-a-module";
914+
if (m.find("Unexpected token") != std::string::npos ||
915+
m.find("SyntaxError") != std::string::npos)
916+
classification = "syntax";
917+
else if (m.find("Cannot use import statement outside a module") != std::string::npos)
918+
classification = "not-a-module";
901919
}
902920
}
903921
}
@@ -915,7 +933,8 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
915933
// Return empty to prevent crashes
916934
return Local<Value>();
917935
} else {
918-
throw NativeScriptException(isolate, tcCompile, "Cannot compile ES module " + canonicalPath);
936+
throw NativeScriptException(isolate, tcCompile,
937+
"Cannot compile ES module " + canonicalPath);
919938
}
920939
}
921940
}
@@ -956,8 +975,11 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
956975
v8::String::Utf8Value w(isolate, msg->Get());
957976
if (*w) {
958977
std::string m(*w);
959-
if (m.find("Cannot find module") != std::string::npos || m.find("failed to resolve module specifier") != std::string::npos) classification = "resolve";
960-
else if (m.find("does not provide an export named") != std::string::npos) classification = "link-export";
978+
if (m.find("Cannot find module") != std::string::npos ||
979+
m.find("failed to resolve module specifier") != std::string::npos)
980+
classification = "resolve";
981+
else if (m.find("does not provide an export named") != std::string::npos)
982+
classification = "link-export";
961983
}
962984
}
963985
}
@@ -974,7 +996,8 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
974996
return Local<Value>();
975997
} else {
976998
if (tcLink.HasCaught()) {
977-
throw NativeScriptException(isolate, tcLink, "Cannot instantiate module " + canonicalPath);
999+
throw NativeScriptException(isolate, tcLink,
1000+
"Cannot instantiate module " + canonicalPath);
9781001
} else {
9791002
// V8 gave no exception object—throw plain text
9801003
throw NativeScriptException(isolate, "Cannot instantiate module " + canonicalPath);
@@ -999,9 +1022,12 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
9991022
v8::String::Utf8Value w(isolate, msg->Get());
10001023
if (*w) {
10011024
std::string m(*w);
1002-
if (m.find("is not defined") != std::string::npos) classification = "reference";
1003-
else if (m.find("TypeError") != std::string::npos) classification = "type";
1004-
else if (m.find("Cannot read properties") != std::string::npos) classification = "type-nullish";
1025+
if (m.find("is not defined") != std::string::npos)
1026+
classification = "reference";
1027+
else if (m.find("TypeError") != std::string::npos)
1028+
classification = "type";
1029+
else if (m.find("Cannot read properties") != std::string::npos)
1030+
classification = "type-nullish";
10051031
}
10061032
}
10071033
}
@@ -1052,7 +1078,7 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
10521078
std::string errorTitle = "Uncaught JavaScript Exception";
10531079
std::string errorMessage = "Module evaluation promise rejected";
10541080
std::string stackTrace = "";
1055-
1081+
10561082
// Try to get the promise result (the actual error)
10571083
Local<Value> reason = promise->Result();
10581084
if (!reason.IsEmpty()) {
@@ -1062,7 +1088,8 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
10621088

10631089
auto messageKey = tns::ToV8String(isolate, "message");
10641090
Local<Value> messageVal;
1065-
if (errorObj->Get(context, messageKey).ToLocal(&messageVal) && messageVal->IsString()) {
1091+
if (errorObj->Get(context, messageKey).ToLocal(&messageVal) &&
1092+
messageVal->IsString()) {
10661093
v8::String::Utf8Value messageUtf8(isolate, messageVal);
10671094
if (*messageUtf8) errorMessage = std::string(*messageUtf8);
10681095
}
@@ -1113,7 +1140,8 @@ ScriptOrigin origin(isolate, urlString, 0, 0, false, -1, Local<Value>(), false,
11131140

11141141
if (IsScriptLoadingLogEnabled()) {
11151142
// Emit a concise summary of the rejection for diagnostics
1116-
std::string stackPreview = stackTrace.size() > 240 ? stackTrace.substr(0, 240) + "" : stackTrace;
1143+
std::string stackPreview =
1144+
stackTrace.size() > 240 ? stackTrace.substr(0, 240) + "" : stackTrace;
11171145
Log(@"[esm][evaluate][promise-rejected:detail] path=%s message=%s stack=%s",
11181146
canonicalPath.c_str(), errorMessage.c_str(), stackPreview.c_str());
11191147
}
@@ -1566,7 +1594,14 @@ throw NativeScriptException(
15661594
}
15671595

15681596
std::string ModuleInternal::GetCacheFileName(const std::string& path) {
1569-
std::string key = path.substr(RuntimeConfig.ApplicationPath.size() + 1);
1597+
std::string key;
1598+
if (path.length() > RuntimeConfig.ApplicationPath.size() &&
1599+
path.compare(0, RuntimeConfig.ApplicationPath.size(), RuntimeConfig.ApplicationPath) == 0) {
1600+
key = path.substr(RuntimeConfig.ApplicationPath.size() + 1);
1601+
} else {
1602+
// Fallback: use the entire path if it doesn't start with ApplicationPath
1603+
key = path;
1604+
}
15701605
std::replace(key.begin(), key.end(), '/', '-');
15711606

15721607
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);

0 commit comments

Comments
 (0)