koffi 0.9.33 → 0.9.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/ffi.hh CHANGED
@@ -100,12 +100,12 @@ struct LibraryHolder {
100
100
  };
101
101
 
102
102
  enum class CallConvention {
103
- Default,
103
+ Cdecl,
104
104
  Stdcall,
105
105
  Fastcall
106
106
  };
107
107
  static const char *const CallConventionNames[] = {
108
- "Default",
108
+ "Cdecl",
109
109
  "Stdcall",
110
110
  "Fastcall"
111
111
  };
package/src/parser.cc ADDED
@@ -0,0 +1,246 @@
1
+ // This program is free software: you can redistribute it and/or modify
2
+ // it under the terms of the GNU Affero General Public License as published by
3
+ // the Free Software Foundation, either version 3 of the License, or
4
+ // (at your option) any later version.
5
+ //
6
+ // This program is distributed in the hope that it will be useful,
7
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
8
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9
+ // GNU Affero General Public License for more details.
10
+ //
11
+ // You should have received a copy of the GNU Affero General Public License
12
+ // along with this program. If not, see https://www.gnu.org/licenses/.
13
+
14
+ #include "vendor/libcc/libcc.hh"
15
+ #include "ffi.hh"
16
+ #include "parser.hh"
17
+
18
+ #include <napi.h>
19
+
20
+ namespace RG {
21
+
22
+ bool PrototypeParser::Parse(Napi::String proto, FunctionInfo *func)
23
+ {
24
+ if (!proto.IsString()) {
25
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for prototype, expected string", GetValueType(instance, proto));
26
+ return false;
27
+ }
28
+
29
+ tokens.Clear();
30
+ offset = 0;
31
+ valid = true;
32
+
33
+ Tokenize(std::string(proto).c_str());
34
+
35
+ func->ret.type = ParseType();
36
+ if (Match("__cdecl")) {
37
+ func->convention = CallConvention::Cdecl;
38
+ } else if (Match("__stdcall")) {
39
+ func->convention = CallConvention::Stdcall;
40
+ } else if (Match("__fastcall")) {
41
+ func->convention = CallConvention::Fastcall;
42
+ }
43
+ func->name = ParseIdentifier();
44
+
45
+ Consume("(");
46
+ if (offset < tokens.len && tokens[offset] != ")") {
47
+ for (;;) {
48
+ ParameterInfo param = {};
49
+
50
+ if (Match("...")) {
51
+ func->variadic = true;
52
+ break;
53
+ }
54
+
55
+ if (Match("_In_")) {
56
+ param.directions = 1;
57
+ } else if (Match("_Out_")) {
58
+ param.directions = 2;
59
+ } else if (Match("_Inout_")) {
60
+ param.directions = 3;
61
+ } else {
62
+ param.directions = 1;
63
+ }
64
+
65
+ param.type = ParseType();
66
+ if (param.type->primitive == PrimitiveKind::Void) {
67
+ MarkError("Type void cannot be used as a parameter");
68
+ return false;
69
+ }
70
+
71
+ if ((param.directions & 2) && (param.type->primitive != PrimitiveKind::Pointer ||
72
+ param.type->ref->primitive != PrimitiveKind::Record)) {
73
+ MarkError("Only object pointers can be used as out parameters (for now)");
74
+ return false;
75
+ }
76
+
77
+ offset += (offset < tokens.len && IsIdentifier(tokens[offset]));
78
+
79
+ if (func->parameters.len >= MaxParameters) {
80
+ MarkError("Functions cannot have more than %1 parameters", MaxParameters);
81
+ return false;
82
+ }
83
+ if ((param.directions & 2) && ++func->out_parameters >= MaxOutParameters) {
84
+ MarkError("Functions cannot have more than out %1 parameters", MaxOutParameters);
85
+ return false;
86
+ }
87
+
88
+ param.offset = func->parameters.len;
89
+
90
+ func->parameters.Append(param);
91
+
92
+ if (offset >= tokens.len || tokens[offset] != ",")
93
+ break;
94
+ offset++;
95
+ }
96
+ }
97
+ Consume(")");
98
+
99
+ Match(";");
100
+ if (offset < tokens.len) {
101
+ MarkError("Unexpected token '%1' after prototype", tokens[offset]);
102
+ }
103
+
104
+ return valid;
105
+ }
106
+
107
+ void PrototypeParser::Tokenize(const char *str)
108
+ {
109
+ for (Size i = 0; str[i]; i++) {
110
+ char c = str[i];
111
+
112
+ if (IsAsciiWhite(c)) {
113
+ continue;
114
+ } else if (IsAsciiAlpha(c) || c == '_') {
115
+ Size j = i;
116
+ while (str[++j] && (IsAsciiAlphaOrDigit(str[j]) || str[j] == '_'));
117
+
118
+ Span<const char> tok = MakeSpan(str + i, j - i);
119
+ tokens.Append(tok);
120
+
121
+ i = j - 1;
122
+ } else if (c == '.' && str[i + 1] == '.' && str[i + 2] == '.') {
123
+ tokens.Append("...");
124
+ i += 2;
125
+ } else {
126
+ Span<const char> tok = MakeSpan(str + i, 1);
127
+ tokens.Append(tok);
128
+ }
129
+ }
130
+ }
131
+
132
+ const TypeInfo *PrototypeParser::ParseType()
133
+ {
134
+ HeapArray<char> buf(&instance->str_alloc);
135
+
136
+ Size indirect = 0;
137
+
138
+ Size start = offset;
139
+ while (offset < tokens.len && IsIdentifier(tokens[offset])) {
140
+ Span<const char> tok = tokens[offset++];
141
+
142
+ if (tok != "const") {
143
+ buf.Append(tok);
144
+ buf.Append(' ');
145
+ }
146
+ }
147
+ if (offset == start) {
148
+ if (offset < tokens.len) {
149
+ MarkError("Unexpected token '%1', expected identifier", tokens[offset]);
150
+ } else {
151
+ MarkError("Unexpected end of prototype, expected identifier");
152
+ }
153
+ return instance->types_map.FindValue("void", nullptr);
154
+ }
155
+ while (offset < tokens.len && tokens[offset] == "*") {
156
+ offset++;
157
+ indirect++;
158
+ }
159
+ buf.ptr[--buf.len] = 0;
160
+
161
+ if (TestStr(buf, "char") && indirect) {
162
+ buf.RemoveFrom(0);
163
+ Fmt(&buf, "string");
164
+
165
+ indirect--;
166
+ }
167
+
168
+ while (buf.len) {
169
+ const TypeInfo *type = instance->types_map.FindValue(buf.ptr, nullptr);
170
+
171
+ if (type) {
172
+ for (Size i = 0; i < indirect; i++) {
173
+ type = GetPointerType(instance, type);
174
+ RG_ASSERT(type);
175
+ }
176
+ return type;
177
+ }
178
+
179
+ // Truncate last token
180
+ {
181
+ Span<const char> remain;
182
+ SplitStrReverse(buf, ' ', &remain);
183
+ buf.len = remain.len;
184
+ buf.ptr[buf.len] = 0;
185
+ }
186
+
187
+ if (indirect) {
188
+ offset -= indirect;
189
+ indirect = 0;
190
+ }
191
+ offset--;
192
+ }
193
+
194
+ MarkError("Unknown type '%1'", tokens[start]);
195
+ return instance->types_map.FindValue("void", nullptr);
196
+ }
197
+
198
+ const char *PrototypeParser::ParseIdentifier()
199
+ {
200
+ if (offset >= tokens.len) {
201
+ MarkError("Unexpected end of prototype, expected identifier");
202
+ return "";
203
+ }
204
+ if (!IsIdentifier(tokens[offset])) {
205
+ MarkError("Unexpected token '%1', expected identifier", tokens[offset]);
206
+ return "";
207
+ }
208
+
209
+ Span<const char> tok = tokens[offset++];
210
+ const char *ident = DuplicateString(tok, &instance->str_alloc).ptr;
211
+
212
+ return ident;
213
+ }
214
+
215
+ bool PrototypeParser::Consume(const char *expect)
216
+ {
217
+ if (offset >= tokens.len) {
218
+ MarkError("Unexpected end of prototype, expected '%1'", expect);
219
+ return false;
220
+ }
221
+ if (tokens[offset] != expect) {
222
+ MarkError("Unexpected token '%1', expected '%2'", tokens[offset], expect);
223
+ return false;
224
+ }
225
+
226
+ offset++;
227
+ return true;
228
+ }
229
+
230
+ bool PrototypeParser::Match(const char *expect)
231
+ {
232
+ if (offset < tokens.len && tokens[offset] == expect) {
233
+ offset++;
234
+ return true;
235
+ } else {
236
+ return false;
237
+ }
238
+ }
239
+
240
+ bool PrototypeParser::IsIdentifier(Span<const char> tok) const
241
+ {
242
+ RG_ASSERT(tok.len);
243
+ return IsAsciiAlpha(tok[0]) || tok[0] == '_';
244
+ }
245
+
246
+ }
package/src/parser.hh ADDED
@@ -0,0 +1,63 @@
1
+ // This program is free software: you can redistribute it and/or modify
2
+ // it under the terms of the GNU Affero General Public License as published by
3
+ // the Free Software Foundation, either version 3 of the License, or
4
+ // (at your option) any later version.
5
+ //
6
+ // This program is distributed in the hope that it will be useful,
7
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
8
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9
+ // GNU Affero General Public License for more details.
10
+ //
11
+ // You should have received a copy of the GNU Affero General Public License
12
+ // along with this program. If not, see https://www.gnu.org/licenses/.
13
+
14
+ #pragma once
15
+
16
+ #include "vendor/libcc/libcc.hh"
17
+ #include "util.hh"
18
+
19
+ #include <napi.h>
20
+
21
+ namespace RG {
22
+
23
+ struct InstanceData;
24
+ struct TypeInfo;
25
+ struct FunctionInfo;
26
+
27
+ class PrototypeParser {
28
+ Napi::Env env;
29
+ InstanceData *instance;
30
+
31
+ // All these members are relevant to the current parse only, and get resetted each time
32
+ HeapArray<Span<const char>> tokens;
33
+ Size offset;
34
+ bool valid;
35
+
36
+ public:
37
+ PrototypeParser(Napi::Env env) : env(env), instance(env.GetInstanceData<InstanceData>()) {}
38
+
39
+ bool Parse(Napi::String proto, FunctionInfo *func);
40
+
41
+ private:
42
+ void Tokenize(const char *str);
43
+
44
+ const TypeInfo *ParseType();
45
+ const char *ParseIdentifier();
46
+
47
+ bool Consume(const char *expect);
48
+ bool Match(const char *expect);
49
+
50
+ bool IsIdentifier(Span<const char> tok) const;
51
+
52
+ template <typename... Args>
53
+ void MarkError(const char *fmt, Args... args)
54
+ {
55
+ if (valid) {
56
+ ThrowError<Napi::Error>(env, fmt, args...);
57
+ valid = false;
58
+ }
59
+ valid = false;
60
+ }
61
+ };
62
+
63
+ }
package/src/util.cc CHANGED
@@ -54,6 +54,29 @@ const TypeInfo *ResolveType(const InstanceData *instance, Napi::Value value, int
54
54
  }
55
55
  }
56
56
 
57
+ const TypeInfo *GetPointerType(InstanceData *instance, const TypeInfo *ref)
58
+ {
59
+ char name_buf[256];
60
+ Fmt(name_buf, "%1%2*", ref->name, ref->primitive == PrimitiveKind::Pointer ? "" : " ");
61
+
62
+ TypeInfo *type = instance->types_map.FindValue(name_buf, nullptr);
63
+
64
+ if (!type) {
65
+ type = instance->types.AppendDefault();
66
+
67
+ type->name = DuplicateString(name_buf, &instance->str_alloc).ptr;
68
+
69
+ type->primitive = PrimitiveKind::Pointer;
70
+ type->size = RG_SIZE(void *);
71
+ type->align = RG_SIZE(void *);
72
+ type->ref = ref;
73
+
74
+ instance->types_map.Set(type);
75
+ }
76
+
77
+ return type;
78
+ }
79
+
57
80
  const char *GetValueType(const InstanceData *instance, Napi::Value value)
58
81
  {
59
82
  for (const TypeInfo &type: instance->types) {
package/src/util.hh CHANGED
@@ -20,6 +20,8 @@
20
20
  namespace RG {
21
21
 
22
22
  struct InstanceData;
23
+ struct TypeInfo;
24
+ struct FunctionInfo;
23
25
 
24
26
  template <typename T, typename... Args>
25
27
  void ThrowError(Napi::Env env, const char *msg, Args... args)
@@ -52,6 +54,7 @@ static inline T *AlignDown(T *ptr, Size align)
52
54
  }
53
55
 
54
56
  const TypeInfo *ResolveType(const InstanceData *instance, Napi::Value value, int *out_directions = nullptr);
57
+ const TypeInfo *GetPointerType(InstanceData *instance, const TypeInfo *type);
55
58
 
56
59
  // Can be slow, only use for error messages
57
60
  const char *GetValueType(const InstanceData *instance, Napi::Value value);
@@ -21,6 +21,13 @@
21
21
  "shutdown": "sudo poweroff"
22
22
  },
23
23
 
24
+ "builds": {
25
+ "Linux ARM32": {
26
+ "directory": "/home/debian/luigi",
27
+ "build": "node ../cnoke/cnoke.js"
28
+ }
29
+ },
30
+
24
31
  "tests": {
25
32
  "Linux ARM32": {
26
33
  "directory": "/home/debian/luigi",
@@ -58,6 +65,13 @@
58
65
  "shutdown": "sudo poweroff"
59
66
  },
60
67
 
68
+ "builds": {
69
+ "Linux ARM64": {
70
+ "directory": "/home/debian/luigi",
71
+ "build": "node ../cnoke/cnoke.js"
72
+ }
73
+ },
74
+
61
75
  "tests": {
62
76
  "Linux ARM64": {
63
77
  "directory": "/home/debian/luigi",
@@ -95,6 +109,13 @@
95
109
  "shutdown": "sudo poweroff"
96
110
  },
97
111
 
112
+ "builds": {
113
+ "Linux i386": {
114
+ "directory": "/home/debian/luigi",
115
+ "build": "node ../cnoke/cnoke.js"
116
+ }
117
+ },
118
+
98
119
  "tests": {
99
120
  "Linux i386": {
100
121
  "directory": "/home/debian/luigi",
@@ -132,6 +153,13 @@
132
153
  "shutdown": "sudo poweroff"
133
154
  },
134
155
 
156
+ "builds": {
157
+ "Linux x64": {
158
+ "directory": "/home/debian/luigi",
159
+ "build": "node ../cnoke/cnoke.js"
160
+ }
161
+ },
162
+
135
163
  "tests": {
136
164
  "Linux x64": {
137
165
  "directory": "/home/debian/luigi",
@@ -169,6 +197,20 @@
169
197
  "shutdown": "shutdown -s -t 0"
170
198
  },
171
199
 
200
+ "builds": {
201
+ "Windows i386": {
202
+ "arch": "ia32",
203
+ "directory": "C:/Users/windows/Desktop/luigi32",
204
+ "build": "C:\\Node32\\node32.cmd node ../cnoke/cnoke.js"
205
+ },
206
+
207
+ "Windows x64": {
208
+ "arch": "x64",
209
+ "directory": "C:/Users/windows/Desktop/luigi64",
210
+ "build": "C:\\Node64\\node64.cmd node ../cnoke/cnoke.js"
211
+ }
212
+ },
213
+
172
214
  "tests": {
173
215
  "Windows i386": {
174
216
  "directory": "C:/Users/windows/Desktop/luigi32",
@@ -218,6 +260,13 @@
218
260
  "shutdown": "sudo poweroff"
219
261
  },
220
262
 
263
+ "builds": {
264
+ "FreeBSD x64": {
265
+ "directory": "/home/freebsd/luigi",
266
+ "build": "node ../cnoke/cnoke.js"
267
+ }
268
+ },
269
+
221
270
  "tests": {
222
271
  "FreeBSD x64": {
223
272
  "directory": "/home/freebsd/luigi",
@@ -255,6 +304,13 @@
255
304
  "shutdown": "sudo poweroff"
256
305
  },
257
306
 
307
+ "builds": {
308
+ "FreeBSD i386": {
309
+ "directory": "/home/freebsd/luigi",
310
+ "build": "node ../cnoke/cnoke.js"
311
+ }
312
+ },
313
+
258
314
  "tests": {
259
315
  "FreeBSD i386": {
260
316
  "directory": "/home/freebsd/luigi",
@@ -292,6 +348,13 @@
292
348
  "shutdown": "sudo poweroff"
293
349
  },
294
350
 
351
+ "builds": {
352
+ "FreeBSD ARM64": {
353
+ "directory": "/home/freebsd/luigi",
354
+ "build": "node ../cnoke/cnoke.js"
355
+ }
356
+ },
357
+
295
358
  "tests": {
296
359
  "FreeBSD ARM64": {
297
360
  "directory": "/home/freebsd/luigi",
@@ -329,6 +392,13 @@
329
392
  "shutdown": "sudo shutdown -h now"
330
393
  },
331
394
 
395
+ "builds": {
396
+ "macOS x64": {
397
+ "directory": "/Users/macos/luigi",
398
+ "build": "PATH=/usr/local/bin:/usr/bin:/bin SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX11.3.sdk node ../cnoke/cnoke.js"
399
+ }
400
+ },
401
+
332
402
  "tests": {
333
403
  "macOS x64": {
334
404
  "directory": "/Users/macos/luigi",
package/test/test.js CHANGED
@@ -24,6 +24,7 @@ const { spawn, spawnSync } = require('child_process');
24
24
  const { NodeSSH } = require('node-ssh');
25
25
  const chalk = require('chalk');
26
26
  const minimatch = require('minimatch');
27
+ const tar = require('tar');
27
28
 
28
29
  // Globals
29
30
 
@@ -56,6 +57,7 @@ async function main() {
56
57
 
57
58
  if (process.argv.length >= 3 && process.argv[2][0] != '-') {
58
59
  switch (process.argv[2]) {
60
+ case 'build': { command = build; i++ } break;
59
61
  case 'test': { command = test; i++; } break;
60
62
  case 'start': { command = start; i++; } break;
61
63
  case 'stop': { command = stop; i++; } break;
@@ -261,10 +263,109 @@ async function start(detach = true) {
261
263
  return success;
262
264
  }
263
265
 
264
- async function test() {
266
+ async function build() {
267
+ let success = true;
268
+
269
+ success &= await start(false);
270
+ success &= await copy(machine => Object.values(machine.builds).map(build => build.directory));
271
+
272
+ console.log('>> Run build commands...');
273
+ await Promise.all(machines.map(async machine => {
274
+ if (ignore.has(machine))
275
+ return;
276
+
277
+ await Promise.all(Object.keys(machine.builds).map(async suite => {
278
+ let build = machine.builds[suite];
279
+
280
+ let cmd = build.build;
281
+ let cwd = build.directory + '/koffi';
282
+
283
+ let start = process.hrtime.bigint();
284
+ let ret = await exec_remote(machine, cmd, cwd);
285
+ let time = Number((process.hrtime.bigint() - start) / 1000000n);
286
+
287
+ if (ret.code == 0) {
288
+ log(machine, `${suite} > Build`, chalk.bold.green(`[${(time / 1000).toFixed(2)}s]`));
289
+ } else {
290
+ log(machine, `${suite} > Build`, chalk.bold.red('[error]'));
291
+
292
+ if (ret.stdout || ret.stderr)
293
+ console.error('');
294
+
295
+ let align = log.align + 9;
296
+ if (ret.stdout) {
297
+ let str = ' '.repeat(align) + 'Standard output:\n' +
298
+ chalk.yellow(ret.stdout.replace(/^/gm, ' '.repeat(align + 4))) + '\n';
299
+ console.error(str);
300
+ }
301
+ if (ret.stderr) {
302
+ let str = ' '.repeat(align) + 'Standard error:\n' +
303
+ chalk.yellow(ret.stderr.replace(/^/gm, ' '.repeat(align + 4))) + '\n';
304
+ console.error(str);
305
+ }
306
+
307
+ success = false;
308
+ }
309
+ }));
310
+ }));
311
+
312
+ console.log('>> Get build artifacts');
313
+ {
314
+ let json = fs.readFileSync(root_dir + '/koffi/package.json', { encoding: 'utf-8' });
315
+ let version = JSON.parse(json).version;
316
+
317
+ await Promise.all(machines.map(async machine => {
318
+ if (ignore.has(machine))
319
+ return;
320
+
321
+ let copied = true;
322
+
323
+ await Promise.all(Object.keys(machine.builds).map(async suite => {
324
+ let build = machine.builds[suite];
325
+
326
+ let platform = build.platform || machine.info.platform;
327
+ let arch = build.arch || machine.info.arch;
328
+
329
+ let src_dir = build.directory + '/koffi/build';
330
+ let dest_dir = root_dir + `/koffi/build/qemu/${version}/koffi_${platform}_${arch}`;
331
+
332
+ fs.mkdirSync(dest_dir + '/build', { mode: 0o755, recursive: true });
333
+
334
+ try {
335
+ await machine.ssh.getDirectory(dest_dir + '/build', src_dir, {
336
+ recursive: false,
337
+ concurrency: 4
338
+ });
339
+
340
+ tar.c({
341
+ gzip: true,
342
+ file: dest_dir + '.tar.gz',
343
+ sync: true,
344
+ cwd: dest_dir + '/..'
345
+ }, [path.basename(dest_dir)]);
346
+ } catch (err) {
347
+ console.log(err);
348
+ ignore.add(machine);
349
+ success = false;
350
+ copied = false;
351
+ }
352
+ }));
353
+
354
+ let status = copied ? chalk.bold.green('[ok]') : chalk.bold.red('[error]');
355
+ log(machine, 'Download', status);
356
+ }));
357
+ }
358
+
359
+ if (machines.some(machine => machine.started))
360
+ success &= await stop(false);
361
+
362
+ return success;
363
+ }
364
+
365
+ async function copy(func) {
265
366
  let success = true;
266
367
 
267
- let snapshot_dir = fs.mkdtempSync(path.join(os.tmpdir(), 'luigi-'));
368
+ let snapshot_dir = fs.mkdtempSync(path.join(os.tmpdir(), 'luigi_'));
268
369
  process.on('exit', () => unlink_recursive(snapshot_dir));
269
370
 
270
371
  console.log('>> Snapshot code...');
@@ -279,8 +380,6 @@ async function test() {
279
380
  basename !== 'luiggi';
280
381
  });
281
382
 
282
- success &= await start(false);
283
-
284
383
  console.log('>> Copy source code...');
285
384
  await Promise.all(machines.map(async machine => {
286
385
  if (ignore.has(machine))
@@ -288,15 +387,15 @@ async function test() {
288
387
 
289
388
  let copied = true;
290
389
 
291
- for (let test of Object.values(machine.tests)) {
390
+ for (let directory of func(machine)) {
292
391
  try {
293
- await machine.ssh.exec('rm', ['-rf', test.directory]);
392
+ await machine.ssh.exec('rm', ['-rf', directory]);
294
393
  } catch (err) {
295
394
  // Fails often on Windows (busy directory or whatever), but rarely a problem
296
395
  }
297
396
 
298
397
  try {
299
- await machine.ssh.putDirectory(snapshot_dir, test.directory, {
398
+ await machine.ssh.putDirectory(snapshot_dir, directory, {
300
399
  recursive: true,
301
400
  concurrency: 4
302
401
  });
@@ -311,6 +410,15 @@ async function test() {
311
410
  log(machine, 'Copy', status);
312
411
  }));
313
412
 
413
+ return success;
414
+ }
415
+
416
+ async function test() {
417
+ let success = true;
418
+
419
+ success &= await start(false);
420
+ success &= await copy(machine => Object.values(machine.tests).map(test => test.directory));
421
+
314
422
  console.log('>> Run test commands...');
315
423
  await Promise.all(machines.map(async machine => {
316
424
  if (ignore.has(machine))