1717import java .util .Collections ;
1818import java .util .List ;
1919import java .util .Map ;
20+ import java .util .concurrent .TimeUnit ;
2021
2122import com .google .gson .JsonArray ;
2223import com .google .gson .JsonElement ;
3738import com .semmle .util .exception .InterruptedError ;
3839import com .semmle .util .exception .ResourceError ;
3940import com .semmle .util .exception .UserError ;
40- import com .semmle .util .io .WholeIO ;
4141import com .semmle .util .logging .LogbackUtils ;
4242import com .semmle .util .process .AbstractProcessBuilder ;
4343import com .semmle .util .process .Builder ;
@@ -114,6 +114,18 @@ public class TypeScriptParser {
114114 */
115115 public static final String TYPESCRIPT_NODE_FLAGS = "SEMMLE_TYPESCRIPT_NODE_FLAGS" ;
116116
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+
117129 /** The Node.js parser wrapper process, if it has been started already. */
118130 private Process parserWrapperProcess ;
119131
@@ -250,7 +262,7 @@ private void setupParserWrapper() {
250262 int mainMemoryMb =
251263 typescriptRam != 0
252264 ? typescriptRam
253- : getMegabyteCountFromPrefixedEnv (TYPESCRIPT_RAM_SUFFIX , 1000 );
265+ : getMegabyteCountFromPrefixedEnv (TYPESCRIPT_RAM_SUFFIX , 2000 );
254266 int reserveMemoryMb = getMegabyteCountFromPrefixedEnv (TYPESCRIPT_RAM_RESERVE_SUFFIX , 400 );
255267
256268 File parserWrapper = getParserWrapper ();
@@ -318,15 +330,7 @@ private JsonObject talkToParserWrapper(JsonObject request) {
318330 if (parserWrapperProcess == null ) setupParserWrapper ();
319331
320332 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 );
330334 }
331335
332336 String response = null ;
@@ -335,31 +339,52 @@ private JsonObject talkToParserWrapper(JsonObject request) {
335339 toParserWrapper .newLine ();
336340 toParserWrapper .flush ();
337341 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+ }
345350 } catch (IOException e ) {
346351 throw new CatastrophicError (
347352 "Could not communicate with TypeScript parser wrapper "
348353 + "(command: ."
349354 + parserWrapperCommand
350355 + ")." ,
351356 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 );
360357 }
361358 }
362359
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+
363388 /**
364389 * Returns the AST for a given source file.
365390 *
0 commit comments