17
17
import java .util .Collections ;
18
18
import java .util .List ;
19
19
import java .util .Map ;
20
+ import java .util .concurrent .TimeUnit ;
20
21
21
22
import com .google .gson .JsonArray ;
22
23
import com .google .gson .JsonElement ;
37
38
import com .semmle .util .exception .InterruptedError ;
38
39
import com .semmle .util .exception .ResourceError ;
39
40
import com .semmle .util .exception .UserError ;
40
- import com .semmle .util .io .WholeIO ;
41
41
import com .semmle .util .logging .LogbackUtils ;
42
42
import com .semmle .util .process .AbstractProcessBuilder ;
43
43
import com .semmle .util .process .Builder ;
@@ -114,6 +114,18 @@ public class TypeScriptParser {
114
114
*/
115
115
public static final String TYPESCRIPT_NODE_FLAGS = "SEMMLE_TYPESCRIPT_NODE_FLAGS" ;
116
116
117
+ /**
118
+ * Exit code for Node.js in case of a fatal error from V8. This exit code sometimes occurs
119
+ * when the process runs out of memory.
120
+ */
121
+ private static final int NODEJS_EXIT_CODE_FATAL_ERROR = 5 ;
122
+
123
+ /**
124
+ * Exit code for Node.js in case it exits due to <code>SIGABRT</code>. This exit code sometimes occurs
125
+ * when the process runs out of memory.
126
+ */
127
+ private static final int NODEJS_EXIT_CODE_SIG_ABORT = 128 + 6 ;
128
+
117
129
/** The Node.js parser wrapper process, if it has been started already. */
118
130
private Process parserWrapperProcess ;
119
131
@@ -250,7 +262,7 @@ private void setupParserWrapper() {
250
262
int mainMemoryMb =
251
263
typescriptRam != 0
252
264
? typescriptRam
253
- : getMegabyteCountFromPrefixedEnv (TYPESCRIPT_RAM_SUFFIX , 1000 );
265
+ : getMegabyteCountFromPrefixedEnv (TYPESCRIPT_RAM_SUFFIX , 2000 );
254
266
int reserveMemoryMb = getMegabyteCountFromPrefixedEnv (TYPESCRIPT_RAM_RESERVE_SUFFIX , 400 );
255
267
256
268
File parserWrapper = getParserWrapper ();
@@ -318,15 +330,7 @@ private JsonObject talkToParserWrapper(JsonObject request) {
318
330
if (parserWrapperProcess == null ) setupParserWrapper ();
319
331
320
332
if (!parserWrapperProcess .isAlive ()) {
321
- int exitCode = 0 ;
322
- try {
323
- exitCode = parserWrapperProcess .waitFor ();
324
- } catch (InterruptedException e ) {
325
- Exceptions .ignore (e , "This is for diagnostic purposes only." );
326
- }
327
- String err = new WholeIO ().strictReadString (parserWrapperProcess .getErrorStream ());
328
- throw new CatastrophicError (
329
- "TypeScript parser wrapper terminated with exit code " + exitCode + "; stderr: " + err );
333
+ throw getExceptionFromMalformedResponse (null , null );
330
334
}
331
335
332
336
String response = null ;
@@ -335,31 +339,52 @@ private JsonObject talkToParserWrapper(JsonObject request) {
335
339
toParserWrapper .newLine ();
336
340
toParserWrapper .flush ();
337
341
response = fromParserWrapper .readLine ();
338
- if (response == null )
339
- throw new CatastrophicError (
340
- "Could not communicate with TypeScript parser wrapper "
341
- + "(command: "
342
- + parserWrapperCommand
343
- + ")." );
344
- return new JsonParser ().parse (response ).getAsJsonObject ();
342
+ if (response == null || response .isEmpty ()) {
343
+ throw getExceptionFromMalformedResponse (response , null );
344
+ }
345
+ try {
346
+ return new JsonParser ().parse (response ).getAsJsonObject ();
347
+ } catch (JsonParseException | IllegalStateException e ) {
348
+ throw getExceptionFromMalformedResponse (response , e );
349
+ }
345
350
} catch (IOException e ) {
346
351
throw new CatastrophicError (
347
352
"Could not communicate with TypeScript parser wrapper "
348
353
+ "(command: ."
349
354
+ parserWrapperCommand
350
355
+ ")." ,
351
356
e );
352
- } catch (JsonParseException | IllegalStateException e ) {
353
- throw new CatastrophicError (
354
- "TypeScript parser wrapper sent unexpected response: "
355
- + response
356
- + " (command: "
357
- + parserWrapperCommand
358
- + ")." ,
359
- e );
360
357
}
361
358
}
362
359
360
+ /**
361
+ * Creates an exception object describing the best known reason for the TypeScript parser wrapper
362
+ * failing to behave as expected.
363
+ *
364
+ * Note that the stderr stream is redirected to our stderr so a more descriptive error is likely
365
+ * to be found in the log, but we try to make the Java exception descriptive as well.
366
+ */
367
+ private RuntimeException getExceptionFromMalformedResponse (String response , Exception e ) {
368
+ try {
369
+ Integer exitCode = null ;
370
+ if (parserWrapperProcess .waitFor (1L , TimeUnit .SECONDS )) {
371
+ exitCode = parserWrapperProcess .waitFor ();
372
+ }
373
+ if (exitCode != null && (exitCode == NODEJS_EXIT_CODE_FATAL_ERROR || exitCode == NODEJS_EXIT_CODE_SIG_ABORT )) {
374
+ return new ResourceError ("The TypeScript parser wrapper crashed, possibly from running out of memory." , e );
375
+ }
376
+ if (exitCode != null ) {
377
+ return new CatastrophicError ("The TypeScript parser wrapper crashed with exit code " + exitCode );
378
+ }
379
+ } catch (InterruptedException e1 ) {
380
+ Exceptions .ignore (e , "This is for diagnostic purposes only." );
381
+ }
382
+ if (response == null ) {
383
+ return new CatastrophicError ("No response from TypeScript parser wrapper" , e );
384
+ }
385
+ return new CatastrophicError ("Unexpected response from TypeScript parser wrapper:\n " + response , e );
386
+ }
387
+
363
388
/**
364
389
* Returns the AST for a given source file.
365
390
*
0 commit comments