3
3
import java .nio .file .Path ;
4
4
import java .util .ArrayList ;
5
5
import java .util .Collections ;
6
+ import java .util .HashSet ;
7
+ import java .util .Arrays ;
6
8
import java .util .List ;
7
9
import java .util .Set ;
8
10
import java .util .Stack ;
@@ -332,17 +334,28 @@ private static class Context {
332
334
private final Label parent ;
333
335
private final int childIndex ;
334
336
private final IdContext idcontext ;
337
+ private final boolean binopOperand ;
335
338
336
339
public Context (Label parent , int childIndex , IdContext idcontext ) {
340
+ this (parent , childIndex , idcontext , false );
341
+ }
342
+
343
+ public Context (Label parent , int childIndex , IdContext idcontext , boolean binopOperand ) {
337
344
this .parent = parent ;
338
345
this .childIndex = childIndex ;
339
346
this .idcontext = idcontext ;
347
+ this .binopOperand = binopOperand ;
340
348
}
341
349
342
350
/** True if the visited AST node occurs as part of a type annotation. */
343
351
public boolean isInsideType () {
344
352
return idcontext .isInsideType ();
345
353
}
354
+
355
+ /** True if the visited AST node occurs as one of the operands of a binary operation. */
356
+ public boolean isBinopOperand () {
357
+ return binopOperand ;
358
+ }
346
359
}
347
360
348
361
private class V extends DefaultVisitor <Context , Label > {
@@ -358,16 +371,24 @@ public V(Platform platform, SourceType sourceType) {
358
371
}
359
372
360
373
private Label visit (INode child , Label parent , int childIndex ) {
361
- return visit (child , parent , childIndex , IdContext .VAR_BIND );
374
+ return visit (child , parent , childIndex , IdContext .VAR_BIND , false );
362
375
}
363
376
364
377
private Label visitAll (List <? extends INode > children , Label parent ) {
365
378
return visitAll (children , parent , IdContext .VAR_BIND , 0 );
366
379
}
367
380
368
381
private Label visit (INode child , Label parent , int childIndex , IdContext idContext ) {
382
+ return visit (child , parent , childIndex , idContext , false );
383
+ }
384
+
385
+ private Label visit (INode child , Label parent , int childIndex , boolean binopOperand ) {
386
+ return visit (child , parent , childIndex , IdContext .VAR_BIND , binopOperand );
387
+ }
388
+
389
+ private Label visit (INode child , Label parent , int childIndex , IdContext idContext , boolean binopOperand ) {
369
390
if (child == null ) return null ;
370
- return child .accept (this , new Context (parent , childIndex , idContext ));
391
+ return child .accept (this , new Context (parent , childIndex , idContext , binopOperand ));
371
392
}
372
393
373
394
private Label visitAll (
@@ -379,7 +400,7 @@ private Label visitAll(
379
400
List <? extends INode > children , Label parent , IdContext idContext , int index , int step ) {
380
401
Label res = null ;
381
402
for (INode child : children ) {
382
- res = visit (child , parent , index , idContext );
403
+ res = visit (child , parent , index , idContext , false );
383
404
index += step ;
384
405
}
385
406
return res ;
@@ -567,12 +588,17 @@ public Label visit(Literal nd, Context c) {
567
588
String valueString = nd .getStringValue ();
568
589
569
590
trapwriter .addTuple ("literals" , valueString , source , key );
591
+ Position start = nd .getLoc ().getStart ();
592
+ com .semmle .util .locations .Position startPos = new com .semmle .util .locations .Position (start .getLine (), start .getColumn () + 1 /* Convert from 0-based to 1-based. */ , start .getOffset ());
593
+
570
594
if (nd .isRegExp ()) {
571
595
OffsetTranslation offsets = new OffsetTranslation ();
572
596
offsets .set (0 , 1 ); // skip the initial '/'
573
- regexpExtractor .extract (source .substring (1 , source .lastIndexOf ('/' )), offsets , nd , false );
574
- } else if (nd .isStringLiteral () && !c .isInsideType () && nd .getRaw ().length () < 1000 ) {
575
- regexpExtractor .extract (valueString , makeStringLiteralOffsets (nd .getRaw ()), nd , true );
597
+ SourceMap sourceMap = SourceMap .legacyWithStartPos (SourceMap .fromString (nd .getRaw ()).offsetBy (0 , offsets ), startPos );
598
+ regexpExtractor .extract (source .substring (1 , source .lastIndexOf ('/' )), sourceMap , nd , false );
599
+ } else if (nd .isStringLiteral () && !c .isInsideType () && nd .getRaw ().length () < 1000 && !c .isBinopOperand ()) {
600
+ SourceMap sourceMap = SourceMap .legacyWithStartPos (SourceMap .fromString (nd .getRaw ()).offsetBy (0 , makeStringLiteralOffsets (nd .getRaw ())), startPos );
601
+ regexpExtractor .extract (valueString , sourceMap , nd , true );
576
602
577
603
// Scan the string for template tags, if we're in a context where such tags are relevant.
578
604
if (scopeManager .isInTemplateFile ()) {
@@ -593,6 +619,38 @@ private boolean isOctalDigit(char ch) {
593
619
return '0' <= ch && ch <= '7' ;
594
620
}
595
621
622
+ /**
623
+ * Constant-folds simple string concatenations in `exp` while keeping an offset translation
624
+ * that tracks back to the original source.
625
+ */
626
+ private Pair <String , OffsetTranslation > getStringConcatResult (Expression exp ) {
627
+ if (exp instanceof BinaryExpression ) {
628
+ BinaryExpression be = (BinaryExpression ) exp ;
629
+ if (be .getOperator ().equals ("+" )) {
630
+ Pair <String , OffsetTranslation > left = getStringConcatResult (be .getLeft ());
631
+ Pair <String , OffsetTranslation > right = getStringConcatResult (be .getRight ());
632
+ if (left == null || right == null ) {
633
+ return null ;
634
+ }
635
+ String str = left .fst () + right .fst ();
636
+ if (str .length () > 1000 ) {
637
+ return null ;
638
+ }
639
+
640
+ int delta = be .getRight ().getLoc ().getStart ().getOffset () - be .getLeft ().getLoc ().getStart ().getOffset ();
641
+ int offset = left .fst ().length ();
642
+ return Pair .make (str , left .snd ().append (right .snd (), offset , delta ));
643
+ }
644
+ } else if (exp instanceof Literal ) {
645
+ Literal lit = (Literal ) exp ;
646
+ if (!lit .isStringLiteral ()) {
647
+ return null ;
648
+ }
649
+ return Pair .make (lit .getStringValue (), makeStringLiteralOffsets (lit .getRaw ()));
650
+ }
651
+ return null ;
652
+ }
653
+
596
654
/**
597
655
* Builds a translation from offsets in a string value back to its original raw literal text
598
656
* (including quotes).
@@ -789,11 +847,32 @@ public Label visit(AssignmentExpression nd, Context c) {
789
847
@ Override
790
848
public Label visit (BinaryExpression nd , Context c ) {
791
849
Label key = super .visit (nd , c );
792
- visit (nd .getLeft (), key , 0 );
793
- visit (nd .getRight (), key , 1 );
850
+ visit (nd .getLeft (), key , 0 , true );
851
+ visit (nd .getRight (), key , 1 , true );
852
+ extractRegxpFromBinop (nd , c );
794
853
return key ;
795
854
}
796
855
856
+ private void extractRegxpFromBinop (BinaryExpression nd , Context c ) {
857
+ if (c .isBinopOperand ()) {
858
+ return ;
859
+ }
860
+ Pair <String , OffsetTranslation > concatResult = getStringConcatResult (nd );
861
+ if (concatResult == null ) {
862
+ return ;
863
+ }
864
+ String foldedString = concatResult .fst ();
865
+ if (foldedString .length () > 1000 && !foldedString .trim ().isEmpty ()) {
866
+ return ;
867
+ }
868
+ OffsetTranslation offsets = concatResult .snd ();
869
+ Position start = nd .getLoc ().getStart ();
870
+ com .semmle .util .locations .Position startPos = new com .semmle .util .locations .Position (start .getLine (), start .getColumn () + 1 /* Convert from 0-based to 1-based. */ , start .getOffset ());
871
+ SourceMap sourceMap = SourceMap .legacyWithStartPos (SourceMap .fromString (nd .getLoc ().getSource ()).offsetBy (0 , offsets ), startPos );
872
+ regexpExtractor .extract (foldedString , sourceMap , nd , true );
873
+ return ;
874
+ }
875
+
797
876
@ Override
798
877
public Label visit (ComprehensionBlock nd , Context c ) {
799
878
Label key = super .visit (nd , c );
0 commit comments