Skip to content

Commit df98f92

Browse files
authored
Merge pull request #171 from dokar3/fix/mingw-stack-chk-crash
Fix/mingw stack chk crash
2 parents b83295e + ce5ffbf commit df98f92

File tree

11 files changed

+162
-79
lines changed

11 files changed

+162
-79
lines changed

.github/workflows/benchmark.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ jobs:
3333
# Cross-compile native libraries
3434
- name: Setup Zig
3535
uses: goto-bus-stop/setup-zig@v2
36+
with:
37+
version: 0.12.0
3638

3739
# Run js/ts scripts
3840
- name: Set Bun.js

.github/workflows/build.yaml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ jobs:
1111
build:
1212
strategy:
1313
matrix:
14-
os: [ ubuntu-latest, macos-latest ]
14+
os: [ ubuntu-latest, macos-latest, windows-latest ]
1515

1616
runs-on: ${{ matrix.os }}
17+
env:
18+
ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false
1719

1820
steps:
1921
- uses: actions/checkout@v6
@@ -38,6 +40,8 @@ jobs:
3840
# Cross-compile native libraries
3941
- name: Setup Zig
4042
uses: goto-bus-stop/setup-zig@v2
43+
with:
44+
version: 0.12.0
4145

4246
# Run js/ts scripts
4347
- name: Set Bun.js
@@ -46,18 +50,18 @@ jobs:
4650
bun-version: latest
4751

4852
- name: Install multiplatform JDKs
49-
run: |
50-
bun scripts/setupMultiplatformJdks.ts
51-
printf "Exports:\n$(cat ~/java-home-vars-github.sh)"
52-
source ~/java-home-vars-github.sh
53+
run: bun scripts/setupMultiplatformJdks.ts
5354

5455
- uses: gradle/actions/setup-gradle@v5
5556

5657
- name: Grant execute permission for scripts
58+
if: runner.os != 'Windows'
5759
run: |
5860
chmod +x gradlew
5961
chmod +x quickjs/native/cmake/zig-ar.sh
6062
chmod +x quickjs/native/cmake/zig-ranlib.sh
6163
6264
- name: Build
63-
run: ./gradlew build
65+
run: |
66+
./gradlew publishToMavenLocal
67+
./gradlew build

.github/workflows/publish.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030
# Cross-compile native libraries
3131
- name: Setup Zig
3232
uses: goto-bus-stop/setup-zig@v2
33+
with:
34+
version: 0.12.0
3335

3436
# Run js/ts scripts
3537
- name: Set Bun.js

buildSrc/src/main/kotlin/com/dokar/quickjs/applyQuickJsNativeBuildTasks.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,16 @@ private fun Project.findBuildPlatformsFromStartTaskNames(): List<Platform> {
173173

174174
val isPublishing = taskNames.any { it.contains("publish", ignoreCase = true) }
175175
if (isPublishing) {
176-
return Platform.values().toList()
176+
val allPlatforms = Platform.values().toList()
177+
return when (currentPlatform) {
178+
Platform.linux_x64 -> allPlatforms.filter {
179+
it == Platform.linux_x64 || it == Platform.linux_aarch64
180+
}
181+
Platform.windows_x64 -> allPlatforms.filter { it == Platform.windows_x64 }
182+
Platform.macos_x64,
183+
Platform.macos_aarch64 -> allPlatforms
184+
else -> allPlatforms
185+
}
177186
}
178187

179188
val isBuild = taskNames.any { it.contains("build", ignoreCase = true) }

buildSrc/src/main/kotlin/com/dokar/quickjs/buildQuickJsNativeLibrary.kt

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,22 @@ internal fun Project.buildQuickJsNativeLibrary(
1515
outputDir: File? = null,
1616
withPlatformSuffixIfCopy: Boolean = false,
1717
) {
18-
val libType = if (sharedLib) "shared" else "static"
18+
if (withJni) {
19+
val home = when (platform) {
20+
Platform.windows_x64 -> windowX64JavaHome()
21+
Platform.linux_x64 -> linuxX64JavaHome()
22+
Platform.linux_aarch64 -> linuxAarch64JavaHome()
23+
Platform.macos_x64 -> macosX64JavaHome()
24+
Platform.macos_aarch64 -> macosAarch64JavaHome()
25+
else -> error("Unsupported platform: '$platform'")
26+
}
27+
if (home == null) {
28+
println("Skip building JNI library for '$platform' because JDK is not found.")
29+
return
30+
}
31+
}
1932

33+
val libType = if (sharedLib) "shared" else "static"
2034
println("Building $libType native library for target '$platform'...")
2135

2236
// Ensure scripts in native/cmake are executable
@@ -50,11 +64,11 @@ internal fun Project.buildQuickJsNativeLibrary(
5064

5165
val generateArgs = if (withJni) {
5266
when (platform) {
53-
Platform.windows_x64 -> commonArgs + ninja + javaHomeArg(windowX64JavaHome())
54-
Platform.linux_x64 -> commonArgs + ninja + javaHomeArg(linuxX64JavaHome())
55-
Platform.linux_aarch64 -> commonArgs + ninja + javaHomeArg(linuxAarch64JavaHome())
56-
Platform.macos_x64 -> commonArgs + ninja + javaHomeArg(macosX64JavaHome())
57-
Platform.macos_aarch64 -> commonArgs + ninja + javaHomeArg(macosAarch64JavaHome())
67+
Platform.windows_x64 -> commonArgs + ninja + javaHomeArg(windowX64JavaHome()!!)
68+
Platform.linux_x64 -> commonArgs + ninja + javaHomeArg(linuxX64JavaHome()!!)
69+
Platform.linux_aarch64 -> commonArgs + ninja + javaHomeArg(linuxAarch64JavaHome()!!)
70+
Platform.macos_x64 -> commonArgs + ninja + javaHomeArg(macosX64JavaHome()!!)
71+
Platform.macos_aarch64 -> commonArgs + ninja + javaHomeArg(macosAarch64JavaHome()!!)
5872
else -> error("Unsupported platform: '$platform'")
5973
}
6074
} else {
@@ -207,29 +221,19 @@ internal fun Project.buildQuickJsNativeLibrary(
207221
/// Multiplatform JDK locations
208222

209223
private fun Project.windowX64JavaHome() =
210-
requireNotNull(envVarOrLocalPropOf("JAVA_HOME_WINDOWS_X64")) {
211-
"'JAVA_HOME_WINDOWS_X64' is not found in env vars or local.properties"
212-
}
224+
envVarOrLocalPropOf("JAVA_HOME_WINDOWS_X64")
213225

214226
private fun Project.linuxX64JavaHome() =
215-
requireNotNull(envVarOrLocalPropOf("JAVA_HOME_LINUX_X64")) {
216-
"'JAVA_HOME_LINUX_X64' is not found in env vars or local.properties"
217-
}
227+
envVarOrLocalPropOf("JAVA_HOME_LINUX_X64")
218228

219229
private fun Project.linuxAarch64JavaHome() =
220-
requireNotNull(envVarOrLocalPropOf("JAVA_HOME_LINUX_AARCH64")) {
221-
"'JAVA_HOME_LINUX_AARCH64' is not found env vars or in local.properties"
222-
}
230+
envVarOrLocalPropOf("JAVA_HOME_LINUX_AARCH64")
223231

224232
private fun Project.macosX64JavaHome() =
225-
requireNotNull(envVarOrLocalPropOf("JAVA_HOME_MACOS_X64")) {
226-
"'JAVA_HOME_MACOS_X64' is not found in env vars or local.properties"
227-
}
233+
envVarOrLocalPropOf("JAVA_HOME_MACOS_X64")
228234

229235
private fun Project.macosAarch64JavaHome() =
230-
requireNotNull(envVarOrLocalPropOf("JAVA_HOME_MACOS_AARCH64")) {
231-
"'JAVA_HOME_MACOS_AARCH64' is not found in env vars or local.properties"
232-
}
236+
envVarOrLocalPropOf("JAVA_HOME_MACOS_AARCH64")
233237

234238
private fun Project.envVarOrLocalPropOf(key: String): String? {
235239
val localProperties = Properties()

integration-test/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/

integration-test/build.gradle.kts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import com.dokar.quickjs.disableUnsupportedPlatformTasks
2+
3+
plugins {
4+
alias(libs.plugins.kotlinMultiplatform)
5+
}
6+
7+
val quickjsVersion: String = property("VERSION_NAME") as String
8+
9+
kotlin {
10+
jvm()
11+
mingwX64()
12+
linuxX64()
13+
linuxArm64()
14+
macosX64()
15+
macosArm64()
16+
17+
applyDefaultHierarchyTemplate()
18+
19+
jvmToolchain {
20+
languageVersion.set(JavaLanguageVersion.of(17))
21+
}
22+
23+
sourceSets {
24+
commonTest.dependencies {
25+
implementation(kotlin("test"))
26+
implementation("io.github.dokar3:quickjs-kt:$quickjsVersion")
27+
implementation(libs.kotlinx.coroutines.core)
28+
implementation(libs.kotlinx.coroutines.test)
29+
}
30+
}
31+
}
32+
33+
repositories {
34+
mavenLocal()
35+
mavenCentral()
36+
}
37+
38+
disableUnsupportedPlatformTasks()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import com.dokar.quickjs.QuickJs
2+
import com.dokar.quickjs.quickJs
3+
import kotlinx.coroutines.test.runTest
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
7+
class QuickJsIntegrationTest {
8+
@Test
9+
fun evalExpression() = runTest {
10+
quickJs {
11+
val result = evaluate<Int>("1 + 2")
12+
assertEquals(3, result)
13+
}
14+
}
15+
16+
@Test
17+
fun evalString() = runTest {
18+
quickJs {
19+
val result = evaluate<String>("'hello' + ' ' + 'world'")
20+
assertEquals("hello world", result)
21+
}
22+
}
23+
}

quickjs/native/common/stack_chk.c

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,23 @@
11
#include <stdlib.h>
22
#include <stdint.h>
3-
#include <windows.h>
43

54
#if defined(_WIN32)
65

7-
// Default stack guard value
8-
uintptr_t __stack_chk_guard = 0x595e9fbd94fda766;
6+
// Generate a pseudo-random stack canary at compile time.
7+
// Not cryptographically strong, but varies per build which is better
8+
// than a fully static value. Runtime randomization via constructor is
9+
// not viable because it runs before the Windows runtime is initialized
10+
// in the Kotlin/Native static library context.
11+
#define STACK_CHK_SEED ((uint64_t)(__LINE__) * 7 + __COUNTER__ * 13)
12+
#define STACK_CHK_HASH(s) ((s) ^ ((s) >> 16) ^ ((s) << 32))
13+
uintptr_t __stack_chk_guard = STACK_CHK_HASH(STACK_CHK_SEED + \
14+
(uint64_t)(__DATE__[0]) * 31 + \
15+
(uint64_t)(__DATE__[2]) * 37 + \
16+
(uint64_t)(__TIME__[0]) * 41 + \
17+
(uint64_t)(__TIME__[1]) * 43);
918

1019
void __stack_chk_fail(void) {
1120
abort();
1221
}
1322

14-
typedef BOOLEAN (WINAPI *RtlGenRandomFunc)(PVOID, ULONG);
15-
16-
// Initialize the stack guard with a random value
17-
__attribute__((constructor))
18-
static void __stack_chk_init(void) {
19-
HMODULE hAdvApi32 = LoadLibraryA("advapi32.dll");
20-
if (hAdvApi32) {
21-
RtlGenRandomFunc RtlGenRandom = (RtlGenRandomFunc)GetProcAddress(hAdvApi32, "SystemFunction036");
22-
if (RtlGenRandom) {
23-
uintptr_t random_guard;
24-
if (RtlGenRandom(&random_guard, sizeof(random_guard))) {
25-
__stack_chk_guard = random_guard;
26-
}
27-
}
28-
FreeLibrary(hAdvApi32);
29-
}
30-
}
3123
#endif

scripts/setupMultiplatformJdks.ts

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { $, ShellOutput } from "bun";
1+
// @ts-nocheck
2+
import { $ } from "bun";
23
import * as path from "path";
34
import * as fs from "fs/promises";
45

@@ -11,7 +12,10 @@ type Jdk = {
1112
sha256: string;
1213
};
1314

14-
const USER_HOME = process.env.HOME!;
15+
const USER_HOME = process.env.HOME || process.env.USERPROFILE;
16+
if (!USER_HOME) {
17+
throw new Error("Cannot determine home directory: neither HOME nor USERPROFILE is set");
18+
}
1519
const JDK_ROOT = path.join(USER_HOME, "jdks");
1620

1721
const JDK_LIST: Jdk[] = [
@@ -66,17 +70,16 @@ async function isFileExists(filepath: string) {
6670
}
6771
}
6872

69-
async function fileSha256(filepath: string) {
70-
const out = await $`shasum -a 256 ${filepath}`.text();
71-
return out.split(" ")[0];
73+
async function fileSha256(filepath: string): Promise<string> {
74+
const file = Bun.file(filepath);
75+
const buffer = await file.arrayBuffer();
76+
const hasher = new Bun.CryptoHasher("sha256");
77+
hasher.update(buffer);
78+
return hasher.digest("hex");
7279
}
7380

74-
async function throwIfStderrNotEmpty(output: ShellOutput) {
75-
const err = output.stderr.toString().trim();
76-
if (err.length > 0) {
77-
throw new Error(err);
78-
}
79-
return output;
81+
async function downloadFile(url: string, destPath: string) {
82+
await $`curl -fsSL -o ${destPath} ${url}`;
8083
}
8184

8285
async function downloadAndExtractJdk(jdk: Jdk) {
@@ -94,21 +97,25 @@ async function downloadAndExtractJdk(jdk: Jdk) {
9497
downloaded = true;
9598
} else {
9699
console.log(`Removing broken ${jdkFullName}...`);
97-
throwIfStderrNotEmpty(await $`rm ${filepath}`);
100+
await fs.unlink(filepath);
98101
}
99102
}
100103
if (!downloaded) {
101-
throwIfStderrNotEmpty(await $`wget --quiet -P ${JDK_ROOT} ${jdk.url}`);
104+
await downloadFile(jdk.url, filepath);
102105
}
103106

104107
console.log(`Extracting ${jdkFullName}...`);
105108
const extractPath = path.join(JDK_ROOT, jdk.name);
106109

107-
await $`mkdir ${extractPath}`;
110+
await fs.mkdir(extractPath, { recursive: true });
108111
if (filename.endsWith(".gz")) {
109-
throwIfStderrNotEmpty(await $`tar -xzf ${filepath} -C ${extractPath}`);
112+
await $`tar -xzf ${filepath} -C ${extractPath}`;
110113
} else if (filename.endsWith(".zip")) {
111-
throwIfStderrNotEmpty(await $`unzip -o -q ${filepath} -d ${extractPath}`);
114+
if (process.platform === "win32") {
115+
await $`tar -xf ${filepath} -C ${extractPath}`;
116+
} else {
117+
await $`unzip -o -q ${filepath} -d ${extractPath}`;
118+
}
112119
} else {
113120
throw new Error(`Unknown JDK archive: ${filename}`);
114121
}
@@ -117,22 +124,26 @@ async function downloadAndExtractJdk(jdk: Jdk) {
117124
}
118125

119126
async function createEnvVars(vars: (readonly [string, string])[]) {
127+
// Write directly to GITHUB_ENV if in CI
128+
const githubEnvFile = process.env.GITHUB_ENV;
129+
if (githubEnvFile) {
130+
let content = "";
131+
for (const [varName, varValue] of vars) {
132+
content += `${varName}=${varValue}\n`;
133+
}
134+
await fs.appendFile(githubEnvFile, content);
135+
console.log("Exported env variables to GITHUB_ENV");
136+
return;
137+
}
138+
139+
// Otherwise write shell scripts for local use
120140
const varExportsFilepath = path.join(USER_HOME, "java-home-vars.sh");
121-
const ghVarsExportsFilepath = path.join(
122-
USER_HOME,
123-
"java-home-vars-github.sh"
124-
);
125141

126142
let varExports = "# Generated by the JDK setup script.";
127-
let ghVarExports = "# Generated by the JDK setup script.";
128-
for (const envVar of vars) {
129-
const varName = envVar[0];
130-
const varValue = envVar[1];
143+
for (const [varName, varValue] of vars) {
131144
varExports += `\nexport ${varName}=${varValue}`;
132-
ghVarExports += `\necho "${varName}=${varValue}" >> $GITHUB_ENV`;
133145
}
134146
await fs.writeFile(varExportsFilepath, varExports);
135-
await fs.writeFile(ghVarsExportsFilepath, ghVarExports);
136147

137148
console.log();
138149
console.log(
@@ -141,10 +152,6 @@ async function createEnvVars(vars: (readonly [string, string])[]) {
141152
console.log();
142153
console.log(`>>> source ~/${path.basename(varExportsFilepath)}`);
143154
console.log();
144-
console.log("Or export env variables in GitHub CI:");
145-
console.log();
146-
console.log(`>>> source ~/${path.basename(ghVarsExportsFilepath)}`);
147-
console.log();
148155
}
149156

150157
const start = Date.now();
@@ -155,7 +162,7 @@ console.log();
155162
console.log("JDK ROOT:", JDK_ROOT);
156163
console.log();
157164

158-
await $`mkdir ${JDK_ROOT}`;
165+
await fs.mkdir(JDK_ROOT, { recursive: true });
159166

160167
// Download and extract
161168
const envVars = await Promise.all(

0 commit comments

Comments
 (0)