Merge pull request #1872 from esben-semmle/js/extraction_metrics

Approved by xiemaisi
This commit is contained in:
semmle-qlci
2019-09-09 10:45:33 +01:00
committed by GitHub
42 changed files with 3447 additions and 160 deletions

View File

@@ -35,7 +35,6 @@ import com.semmle.ts.ast.InterfaceDeclaration;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.NamespaceDeclaration;
@@ -49,6 +48,7 @@ import com.semmle.ts.ast.TypeAssertion;
import com.semmle.ts.ast.TypeExpression;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.ts.ast.TypeofTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.UnionTypeExpr;
import com.semmle.util.exception.CatastrophicError;

View File

@@ -31,7 +31,6 @@ import com.semmle.ts.ast.InterfaceDeclaration;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.NamespaceDeclaration;
@@ -44,6 +43,7 @@ import com.semmle.ts.ast.TypeAliasDeclaration;
import com.semmle.ts.ast.TypeAssertion;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.ts.ast.TypeofTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.UnionTypeExpr;
/**

View File

@@ -1,11 +1,5 @@
package com.semmle.js.extractor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import com.semmle.js.ast.AClass;
import com.semmle.js.ast.AFunction;
import com.semmle.js.ast.AFunctionExpression;
@@ -104,6 +98,7 @@ import com.semmle.js.ast.jsx.JSXMemberExpression;
import com.semmle.js.ast.jsx.JSXNamespacedName;
import com.semmle.js.ast.jsx.JSXOpeningElement;
import com.semmle.js.ast.jsx.JSXSpreadAttribute;
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
import com.semmle.js.extractor.ExtractorConfig.Platform;
import com.semmle.js.extractor.ExtractorConfig.SourceType;
import com.semmle.js.extractor.ScopeManager.DeclKind;
@@ -149,6 +144,11 @@ import com.semmle.ts.ast.UnionTypeExpr;
import com.semmle.util.collections.CollectionUtil;
import com.semmle.util.trap.TrapWriter;
import com.semmle.util.trap.TrapWriter.Label;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Stack;
/** Extractor for AST-based information; invoked by the {@link JSExtractor}. */
public class ASTExtractor {
@@ -193,6 +193,10 @@ public class ASTExtractor {
return scopeManager;
}
public ExtractionMetrics getMetrics() {
return lexicalExtractor.getMetrics();
}
/**
* The binding semantics for an identifier.
*
@@ -1946,9 +1950,11 @@ public class ASTExtractor {
}
public void extract(Node root, Platform platform, SourceType sourceType, int toplevelKind) {
lexicalExtractor.getMetrics().startPhase(ExtractionPhase.ASTExtractor_extract);
trapwriter.addTuple("toplevels", toplevelLabel, toplevelKind);
locationManager.emitNodeLocation(root, toplevelLabel);
root.accept(new V(platform, sourceType), null);
lexicalExtractor.getMetrics().stopPhase(ExtractionPhase.ASTExtractor_extract);
}
}

View File

@@ -1,5 +1,26 @@
package com.semmle.js.extractor;
import com.semmle.js.extractor.ExtractorConfig.SourceType;
import com.semmle.js.extractor.FileExtractor.FileType;
import com.semmle.js.extractor.trapcache.DefaultTrapCache;
import com.semmle.js.extractor.trapcache.DummyTrapCache;
import com.semmle.js.extractor.trapcache.ITrapCache;
import com.semmle.js.parser.ParsedProject;
import com.semmle.js.parser.TypeScriptParser;
import com.semmle.ts.extractor.TypeExtractor;
import com.semmle.ts.extractor.TypeTable;
import com.semmle.util.data.StringUtil;
import com.semmle.util.exception.CatastrophicError;
import com.semmle.util.exception.Exceptions;
import com.semmle.util.exception.ResourceError;
import com.semmle.util.exception.UserError;
import com.semmle.util.extraction.ExtractorOutputConfig;
import com.semmle.util.files.FileUtil;
import com.semmle.util.io.csv.CSVReader;
import com.semmle.util.language.LegacyLanguage;
import com.semmle.util.process.Env;
import com.semmle.util.projectstructure.ProjectLayout;
import com.semmle.util.trap.TrapWriter;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
@@ -27,28 +48,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import com.semmle.js.extractor.ExtractorConfig.SourceType;
import com.semmle.js.extractor.FileExtractor.FileType;
import com.semmle.js.extractor.trapcache.DefaultTrapCache;
import com.semmle.js.extractor.trapcache.DummyTrapCache;
import com.semmle.js.extractor.trapcache.ITrapCache;
import com.semmle.js.parser.ParsedProject;
import com.semmle.js.parser.TypeScriptParser;
import com.semmle.ts.extractor.TypeExtractor;
import com.semmle.ts.extractor.TypeTable;
import com.semmle.util.data.StringUtil;
import com.semmle.util.exception.CatastrophicError;
import com.semmle.util.exception.Exceptions;
import com.semmle.util.exception.ResourceError;
import com.semmle.util.exception.UserError;
import com.semmle.util.extraction.ExtractorOutputConfig;
import com.semmle.util.files.FileUtil;
import com.semmle.util.io.csv.CSVReader;
import com.semmle.util.language.LegacyLanguage;
import com.semmle.util.process.Env;
import com.semmle.util.projectstructure.ProjectLayout;
import com.semmle.util.trap.TrapWriter;
/**
* An alternative entry point to the JavaScript extractor.
*
@@ -71,8 +70,8 @@ import com.semmle.util.trap.TrapWriter;
* patterns that can be used to refine the list of files to include and exclude
* <li><code>LGTM_INDEX_TYPESCRIPT</code>: whether to extract TypeScript
* <li><code>LGTM_INDEX_FILETYPES</code>: a newline-separated list of ".extension:filetype" pairs
* specifying which {@link FileType} to use for the given extension; the additional file
* type <code>XML</code> is also supported
* specifying which {@link FileType} to use for the given extension; the additional file type
* <code>XML</code> is also supported
* <li><code>LGTM_INDEX_XML_MODE</code>: whether to extract XML files
* <li><code>LGTM_THREADS</code>: the maximum number of files to extract in parallel
* <li><code>LGTM_TRAP_CACHE</code>: the path of a directory to use for trap caching
@@ -166,8 +165,8 @@ import com.semmle.util.trap.TrapWriter;
* <p>If <code>LGTM_INDEX_XML_MODE</code> is set to <code>ALL</code>, then all files with extension
* <code>.xml</code> under <code>LGTM_SRC</code> are extracted as XML (in addition to any files
* whose file type is specified to be <code>XML</code> via <code>LGTM_INDEX_SOURCE_TYPE</code>).
* Currently XML extraction does not respect inclusion and exclusion filters, but this is a bug,
* not a feature, and hence will change eventually.
* Currently XML extraction does not respect inclusion and exclusion filters, but this is a bug, not
* a feature, and hence will change eventually.
*
* <p>Note that all these customisations only apply to <code>LGTM_SRC</code>. Extraction of externs
* is not customisable.
@@ -288,8 +287,7 @@ public class AutoBuild {
try {
fileType = StringUtil.uc(fileType);
if ("XML".equals(fileType)) {
if (extension.length() < 2)
throw new UserError("Invalid extension '" + extension + "'.");
if (extension.length() < 2) throw new UserError("Invalid extension '" + extension + "'.");
xmlExtensions.add(extension.substring(1));
} else {
fileTypes.put(extension, FileType.valueOf(fileType));
@@ -304,8 +302,7 @@ public class AutoBuild {
private void setupXmlMode() {
String xmlMode = getEnvVar("LGTM_INDEX_XML_MODE", "DISABLED");
xmlMode = StringUtil.uc(xmlMode.trim());
if ("ALL".equals(xmlMode))
xmlExtensions.add("xml");
if ("ALL".equals(xmlMode)) xmlExtensions.add("xml");
else if (!"DISABLED".equals(xmlMode))
throw new UserError("Invalid XML mode '" + xmlMode + "' (should be either ALL or DISABLED).");
}
@@ -744,8 +741,7 @@ public class AutoBuild {
try {
long start = logBeginProcess("Extracting " + file);
Integer loc = extractor.extract(f, state);
if (!extractor.getConfig().isExterns() && (loc == null || loc != 0))
seenCode = true;
if (!extractor.getConfig().isExterns() && (loc == null || loc != 0)) seenCode = true;
logEndProcess(start, "Done extracting " + file);
} catch (Throwable t) {
System.err.println("Exception while extracting " + file + ".");
@@ -776,8 +772,7 @@ public class AutoBuild {
}
protected void extractXml() throws IOException {
if (xmlExtensions.isEmpty())
return;
if (xmlExtensions.isEmpty()) return;
List<String> cmd = new ArrayList<>();
cmd.add("odasa");
cmd.add("index");

View File

@@ -93,6 +93,7 @@ import com.semmle.js.ast.jsx.JSXMemberExpression;
import com.semmle.js.ast.jsx.JSXNamespacedName;
import com.semmle.js.ast.jsx.JSXOpeningElement;
import com.semmle.js.ast.jsx.JSXSpreadAttribute;
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
import com.semmle.ts.ast.DecoratorList;
import com.semmle.ts.ast.EnumDeclaration;
import com.semmle.ts.ast.EnumMember;
@@ -171,11 +172,13 @@ public class CFGExtractor {
private final TrapWriter trapwriter;
private final Label toplevelLabel;
private final LocationManager locationManager;
private final ExtractionMetrics metrics;
public CFGExtractor(ASTExtractor astExtractor) {
this.trapwriter = astExtractor.getTrapwriter();
this.toplevelLabel = astExtractor.getToplevelLabel();
this.locationManager = astExtractor.getLocationManager();
this.metrics = astExtractor.getMetrics();
}
@SuppressWarnings("unchecked")
@@ -1955,6 +1958,8 @@ public class CFGExtractor {
}
public void extract(Node nd) {
metrics.startPhase(ExtractionPhase.CFGExtractor_extract);
nd.accept(new V(), new SimpleSuccessorInfo(null));
metrics.stopPhase(ExtractionPhase.CFGExtractor_extract);
}
}

View File

@@ -0,0 +1,167 @@
package com.semmle.js.extractor;
import com.semmle.util.trap.TrapWriter;
import com.semmle.util.trap.TrapWriter.Label;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.Stack;
/** Metrics for the (single-threaded) extraction of a single file. */
public class ExtractionMetrics {
/**
* The phase of the extraction that should be measured time for.
*
* <p>Convention: the enum names have the format <code>{ClassName}_{MethodName}</code>, and should
* identify the methods they correspond to.
*/
public enum ExtractionPhase {
ASTExtractor_extract(0),
CFGExtractor_extract(1),
FileExtractor_extractContents(2),
JSExtractor_extract(3),
JSParser_parse(4),
LexicalExtractor_extractLines(5),
LexicalExtractor_extractTokens(6),
TypeScriptASTConverter_convertAST(7),
TypeScriptParser_talkToParserWrapper(8);
/** The id used in the database for the time spent performing this phase of the extraction. */
final int dbschemeId;
ExtractionPhase(int dbschemeId) {
this.dbschemeId = dbschemeId;
}
}
/** The cache file, if any. */
private File cacheFile;
/** True iff the extraction of this file reuses an existing trap cache file. */
private boolean canReuseCacheFile;
/** The cumulative CPU-time spent in each extraction phase so far. */
private final long[] cpuTimes = new long[ExtractionPhase.values().length];
/** The label for the file that is being extracted. */
private Label fileLabel;
/** The number of UTF16 code units in the file that is being extracted. */
private int length;
/** The previous time a CPU-time measure was performed. */
private long previousCpuTime;
/** The previous time a wallclock-time measure was performed. */
private long previousWallclockTime;
/** The extraction phase stack. */
private final Stack<ExtractionPhase> stack = new Stack<>();
/** The current thread, used for measuring CPU-time. */
private final ThreadMXBean thread = ManagementFactory.getThreadMXBean();
/** The cumulative wallclock-time spent in each extraction phase so far. */
private final long[] wallclockTimes = new long[ExtractionPhase.values().length];
/**
* True iff extraction metrics could not be obtained for this file (due to an unforeseen error
* that should not prevent the ordinary extraction from succeeding).
*/
private boolean timingsFailed;
/**
* Writes the data metrics to a trap file. Note that this makes the resulting trap file content
* non-deterministic.
*/
public void writeDataToTrap(TrapWriter trapwriter) {
trapwriter.addTuple(
"extraction_data",
fileLabel,
cacheFile != null ? cacheFile.getAbsolutePath() : "",
canReuseCacheFile,
length);
}
/**
* Writes the timing metrics to a trap file. Note that this makes the resulting trap file content
* non-deterministic.
*/
public void writeTimingsToTrap(TrapWriter trapwriter) {
if (!stack.isEmpty()) {
failTimings(
String.format(
"Could not properly record extraction times for %s. (stack = %s)%n",
fileLabel, stack.toString()));
}
if (!timingsFailed) {
for (int i = 0; i < ExtractionPhase.values().length; i++) {
trapwriter.addTuple("extraction_time", fileLabel, i, 0, (float) cpuTimes[i]);
trapwriter.addTuple("extraction_time", fileLabel, i, 1, (float) wallclockTimes[i]);
}
}
}
private void failTimings(String msg) {
System.err.println(msg);
System.err.flush();
this.timingsFailed = true;
}
private void incrementCurrentTimer() {
long nowWallclock = System.nanoTime();
long nowCpu = thread.getCurrentThreadCpuTime();
if (!stack.isEmpty()) {
// increment by the time elapsed
wallclockTimes[stack.peek().dbschemeId] += nowWallclock - previousWallclockTime;
cpuTimes[stack.peek().dbschemeId] += nowCpu - previousCpuTime;
}
// update the running clock
previousWallclockTime = nowWallclock;
previousCpuTime = nowCpu;
}
public void setCacheFile(File cacheFile) {
this.cacheFile = cacheFile;
}
public void setCanReuseCacheFile(boolean canReuseCacheFile) {
this.canReuseCacheFile = canReuseCacheFile;
}
public void setFileLabel(Label fileLabel) {
this.fileLabel = fileLabel;
}
public void setLength(int length) {
this.length = length;
}
public void startPhase(ExtractionPhase event) {
incrementCurrentTimer();
stack.push(event);
}
public void stopPhase(
ExtractionPhase
event /* technically not needed, but useful for documentation and sanity checking */) {
if (stack.isEmpty()) {
failTimings(
String.format(
"Inconsistent extraction time recording: trying to stop timer %s, but no timer is running",
event));
return;
}
if (stack.peek() != event) {
failTimings(
String.format(
"Inconsistent extraction time recording: trying to stop timer %s, but current timer is: %s",
event, stack.peek()));
return;
}
incrementCurrentTimer();
stack.pop();
}
}

View File

@@ -1,5 +1,6 @@
package com.semmle.js.extractor;
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
import com.semmle.js.extractor.trapcache.CachingTrapWriter;
import com.semmle.js.extractor.trapcache.ITrapCache;
import com.semmle.util.data.StringUtil;
@@ -384,10 +385,9 @@ public class FileExtractor {
return config.hasFileType() || FileType.forFile(f, config) != null;
}
/**
* @return the number of lines of code extracted, or {@code null} if the file was cached
*/
/** @return the number of lines of code extracted, or {@code null} if the file was cached */
public Integer extract(File f, ExtractorState state) throws IOException {
// populate source archive
String source = new WholeIO(config.getDefaultEncoding()).strictread(f);
outputConfig.getSourceArchive().add(f, source);
@@ -395,6 +395,7 @@ public class FileExtractor {
// extract language-independent bits
TrapWriter trapwriter = outputConfig.getTrapWriterFactory().mkTrapWriter(f);
Label fileLabel = trapwriter.populateFile(f);
LocationManager locationManager = new LocationManager(f, trapwriter, fileLabel);
locationManager.emitFileLocation(fileLabel, 0, 0, 0, 0);
@@ -426,22 +427,34 @@ public class FileExtractor {
private Integer extractContents(
File f, Label fileLabel, String source, LocationManager locationManager, ExtractorState state)
throws IOException {
ExtractionMetrics metrics = new ExtractionMetrics();
metrics.startPhase(ExtractionPhase.FileExtractor_extractContents);
metrics.setLength(source.length());
metrics.setFileLabel(fileLabel);
TrapWriter trapwriter = locationManager.getTrapWriter();
FileType fileType = getFileType(f);
File cacheFile = null, // the cache file for this extraction
resultFile = null; // the final result TRAP file for this extraction
// check whether we can perform caching
if (bumpIdCounter(trapwriter) && fileType.isTrapCachingAllowed()) {
if (bumpIdCounter(trapwriter)) {
resultFile = outputConfig.getTrapWriterFactory().getTrapFileFor(f);
if (resultFile != null) cacheFile = trapCache.lookup(source, config, fileType);
}
// check whether we can perform caching
if (resultFile != null && fileType.isTrapCachingAllowed()) {
cacheFile = trapCache.lookup(source, config, fileType);
}
if (cacheFile != null) {
boolean canUseCacheFile = cacheFile != null;
boolean canReuseCacheFile = canUseCacheFile && cacheFile.exists();
metrics.setCacheFile(cacheFile);
metrics.setCanReuseCacheFile(canReuseCacheFile);
metrics.writeDataToTrap(trapwriter);
if (canUseCacheFile) {
FileUtil.close(trapwriter);
if (cacheFile.exists()) {
if (canReuseCacheFile) {
FileUtil.append(cacheFile, resultFile);
return null;
}
@@ -459,18 +472,20 @@ public class FileExtractor {
try {
IExtractor extractor = fileType.mkExtractor(config, state);
TextualExtractor textualExtractor =
new TextualExtractor(trapwriter, locationManager, source, config.getExtractLines());
new TextualExtractor(
trapwriter, locationManager, source, config.getExtractLines(), metrics);
LoCInfo loc = extractor.extract(textualExtractor);
int numLines = textualExtractor.getNumLines();
int linesOfCode = loc.getLinesOfCode(), linesOfComments = loc.getLinesOfComments();
trapwriter.addTuple("numlines", fileLabel, numLines, linesOfCode, linesOfComments);
trapwriter.addTuple("filetype", fileLabel, fileType.toString());
metrics.stopPhase(ExtractionPhase.FileExtractor_extractContents);
metrics.writeTimingsToTrap(trapwriter);
successful = true;
return linesOfCode;
} finally {
if (!successful && trapwriter instanceof CachingTrapWriter)
((CachingTrapWriter) trapwriter).discard();
FileUtil.close(trapwriter);
}
}

View File

@@ -191,7 +191,12 @@ public class HTMLExtractor implements IExtractor {
JSExtractor extractor = new JSExtractor(config);
try {
TextualExtractor tx =
new TextualExtractor(trapwriter, scriptLocationManager, source, config.getExtractLines());
new TextualExtractor(
trapwriter,
scriptLocationManager,
source,
config.getExtractLines(),
textualExtractor.getMetrics());
return extractor.extract(tx, source, toplevelKind, scopeManager).snd();
} catch (ParseError e) {
e.setPosition(scriptLocationManager.translatePosition(e.getPosition()));

View File

@@ -3,6 +3,7 @@ package com.semmle.js.extractor;
import com.semmle.js.ast.Comment;
import com.semmle.js.ast.Node;
import com.semmle.js.ast.Token;
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
import com.semmle.js.extractor.ExtractorConfig.ECMAVersion;
import com.semmle.js.extractor.ExtractorConfig.Platform;
import com.semmle.js.extractor.ExtractorConfig.SourceType;
@@ -52,7 +53,8 @@ public class JSExtractor {
SourceType sourceType = establishSourceType(source, true);
JSParser.Result parserRes = JSParser.parse(config, sourceType, source);
JSParser.Result parserRes =
JSParser.parse(config, sourceType, source, textualExtractor.getMetrics());
return extract(textualExtractor, source, toplevelKind, scopeManager, sourceType, parserRes);
}
@@ -87,6 +89,7 @@ public class JSExtractor {
SourceType sourceType,
JSParser.Result parserRes)
throws ParseError {
textualExtractor.getMetrics().startPhase(ExtractionPhase.JSExtractor_extract);
Label toplevelLabel;
TrapWriter trapwriter = textualExtractor.getTrapwriter();
LocationManager locationManager = textualExtractor.getLocationManager();
@@ -104,7 +107,6 @@ public class JSExtractor {
new LexicalExtractor(textualExtractor, parserRes.getTokens(), parserRes.getComments());
ASTExtractor scriptExtractor = new ASTExtractor(lexicalExtractor, scopeManager);
toplevelLabel = scriptExtractor.getToplevelLabel();
lexicalExtractor.extractComments(toplevelLabel);
loc = lexicalExtractor.extractLines(parserRes.getSource(), toplevelLabel);
lexicalExtractor.extractTokens(toplevelLabel);
@@ -126,7 +128,6 @@ public class JSExtractor {
for (ParseError parseError : parserRes.getErrors()) {
if (!config.isTolerateParseErrors()) throw parseError;
Label key = trapwriter.freshLabel();
String errorLine = textualExtractor.getLine(parseError.getPosition().getLine());
trapwriter.addTuple("jsParseErrors", key, toplevelLabel, "Error: " + parseError, errorLine);
@@ -139,6 +140,8 @@ public class JSExtractor {
if (platform == Platform.NODE && sourceType == SourceType.COMMONJS_MODULE)
textualExtractor.getTrapwriter().addTuple("isNodejs", toplevelLabel);
textualExtractor.getMetrics().stopPhase(ExtractionPhase.JSExtractor_extract);
return Pair.make(toplevelLabel, loc);
}

View File

@@ -4,6 +4,7 @@ import com.semmle.js.ast.Comment;
import com.semmle.js.ast.Position;
import com.semmle.js.ast.SourceElement;
import com.semmle.js.ast.Token;
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
import com.semmle.util.trap.TrapWriter;
import com.semmle.util.trap.TrapWriter.Label;
import java.util.List;
@@ -40,9 +41,16 @@ public class LexicalExtractor {
return comments;
}
public ExtractionMetrics getMetrics() {
return textualExtractor.getMetrics();
}
public LoCInfo extractLines(String src, Label toplevelKey) {
textualExtractor.getMetrics().startPhase(ExtractionPhase.LexicalExtractor_extractLines);
Position end = textualExtractor.extractLines(src, toplevelKey);
return emitNumlines(toplevelKey, new Position(1, 0, 0), end);
LoCInfo info = emitNumlines(toplevelKey, new Position(1, 0, 0), end);
textualExtractor.getMetrics().stopPhase(ExtractionPhase.LexicalExtractor_extractLines);
return info;
}
/**
@@ -112,11 +120,11 @@ public class LexicalExtractor {
}
public void extractTokens(Label toplevelKey) {
textualExtractor.getMetrics().startPhase(ExtractionPhase.LexicalExtractor_extractTokens);
int j = 0;
for (int i = 0, n = tokens.size(), idx = 0; i < n; ++i) {
Token token = tokens.get(i);
if (token == null) continue;
Label key = trapwriter.freshLabel();
int kind = -1;
switch (token.getType()) {
@@ -164,6 +172,7 @@ public class LexicalExtractor {
if (token.getLoc().equals(next.getLoc())) tokens.set(i + 1, null);
}
}
textualExtractor.getMetrics().stopPhase(ExtractionPhase.LexicalExtractor_extractTokens);
}
public void extractComments(Label toplevelKey) {

View File

@@ -37,7 +37,7 @@ public class Main {
* A version identifier that should be updated every time the extractor changes in such a way that
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
*/
public static final String EXTRACTOR_VERSION = "2019-09-02";
public static final String EXTRACTOR_VERSION = "2019-09-04";
public static final Pattern NEWLINE = Pattern.compile("\n");

View File

@@ -20,14 +20,20 @@ public class TextualExtractor {
private final LocationManager locationManager;
private final Label fileLabel;
private final boolean extractLines;
private final ExtractionMetrics metrics;
public TextualExtractor(
TrapWriter trapwriter, LocationManager locationManager, String source, boolean extractLines) {
TrapWriter trapwriter,
LocationManager locationManager,
String source,
boolean extractLines,
ExtractionMetrics metrics) {
this.trapwriter = trapwriter;
this.locationManager = locationManager;
this.source = source;
this.fileLabel = locationManager.getFileLabel();
this.extractLines = extractLines;
this.metrics = metrics;
}
public TrapWriter getTrapwriter() {
@@ -42,6 +48,10 @@ public class TextualExtractor {
return source;
}
public ExtractionMetrics getMetrics() {
return metrics;
}
public String mkToString(SourceElement nd) {
return sanitiseToString(nd.getLoc().getSource());
}

View File

@@ -17,7 +17,6 @@ import com.semmle.ts.ast.InferTypeExpr;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.OptionalTypeExpr;
@@ -26,6 +25,7 @@ import com.semmle.ts.ast.RestTypeExpr;
import com.semmle.ts.ast.TupleTypeExpr;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.ts.ast.TypeofTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.UnionTypeExpr;
import com.semmle.util.exception.CatastrophicError;
@@ -129,8 +129,10 @@ public class TypeExprKinds {
@Override
public Integer visit(UnaryTypeExpr nd, Void c) {
switch (nd.getKind()) {
case Keyof: return keyofTypeExpr;
case Readonly: return readonlyTypeExpr;
case Keyof:
return keyofTypeExpr;
case Readonly:
return readonlyTypeExpr;
}
throw new CatastrophicError("Unhandled UnaryTypeExpr kind: " + nd.getKind());
}

View File

@@ -21,7 +21,7 @@ public class TypeScriptExtractor implements IExtractor {
LocationManager locationManager = textualExtractor.getLocationManager();
String source = textualExtractor.getSource();
File sourceFile = locationManager.getSourceFile();
Result res = parser.parse(sourceFile, source);
Result res = parser.parse(sourceFile, source, textualExtractor.getMetrics());
ScopeManager scopeManager =
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017);
try {

View File

@@ -1,5 +1,14 @@
package com.semmle.js.extractor.test;
import com.semmle.js.extractor.AutoBuild;
import com.semmle.js.extractor.ExtractorState;
import com.semmle.js.extractor.FileExtractor;
import com.semmle.js.extractor.FileExtractor.FileType;
import com.semmle.util.data.StringUtil;
import com.semmle.util.exception.UserError;
import com.semmle.util.files.FileUtil;
import com.semmle.util.files.FileUtil8;
import com.semmle.util.process.Env;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -16,23 +25,12 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import com.semmle.js.extractor.AutoBuild;
import com.semmle.js.extractor.ExtractorState;
import com.semmle.js.extractor.FileExtractor;
import com.semmle.js.extractor.FileExtractor.FileType;
import com.semmle.util.data.StringUtil;
import com.semmle.util.exception.UserError;
import com.semmle.util.files.FileUtil;
import com.semmle.util.files.FileUtil8;
import com.semmle.util.process.Env;
public class AutoBuildTests {
private Path SEMMLE_DIST, LGTM_SRC;
private Set<String> expected;
@@ -132,16 +130,18 @@ public class AutoBuildTests {
@Override
protected void extractXml() throws IOException {
Files.walkFileTree(LGTM_SRC, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
String ext = FileUtil.extension(file);
if (!ext.isEmpty() && getXmlExtensions().contains(ext.substring(1)))
actual.add(file.toString());
return FileVisitResult.CONTINUE;
}
});
Files.walkFileTree(
LGTM_SRC,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
String ext = FileUtil.extension(file);
if (!ext.isEmpty() && getXmlExtensions().contains(ext.substring(1)))
actual.add(file.toString());
return FileVisitResult.CONTINUE;
}
});
}
}.run();
String expectedString = StringUtil.glue("\n", expected.stream().sorted().toArray());

View File

@@ -1,6 +1,7 @@
package com.semmle.js.extractor.test;
import com.semmle.js.ast.Node;
import com.semmle.js.extractor.ExtractionMetrics;
import com.semmle.js.extractor.ExtractorConfig;
import com.semmle.js.extractor.ExtractorConfig.SourceType;
import com.semmle.js.extractor.NodeJSDetector;
@@ -13,7 +14,7 @@ public class NodeJSDetectorTests {
private static final ExtractorConfig CONFIG = new ExtractorConfig(false);
private void isNodeJS(String src, boolean expected) {
Result res = JSParser.parse(CONFIG, SourceType.SCRIPT, src);
Result res = JSParser.parse(CONFIG, SourceType.SCRIPT, src, new ExtractionMetrics());
Node ast = res.getAST();
Assert.assertNotNull(ast);
Assert.assertTrue(NodeJSDetector.looksLikeNodeJS(ast) == expected);

View File

@@ -166,6 +166,16 @@ public class TrapTests {
String expected = new WholeIO().strictreadText(trap);
expectedVsActual.add(Pair.make(expected, actual));
}
@Override
public void addTuple(String tableName, Object... values) {
if ("extraction_data".equals(tableName)
|| "extraction_time".equals(tableName)) {
// ignore non-deterministic tables
return;
}
super.addTuple(tableName, values);
}
};
}

View File

@@ -47,9 +47,8 @@ public class JSDocParser {
// This occurs before the start of 'source', so the lineStart is negative.
int firstLineStart = -(startPos.getColumn() + "/**".length() - 1);
this.absoluteOffset = startPos.getOffset();
Pair<String, List<JSDocTagParser.Tag>> r = p.new TagParser(null).parseComment(
startPos.getLine() - 1,
firstLineStart);
Pair<String, List<JSDocTagParser.Tag>> r =
p.new TagParser(null).parseComment(startPos.getLine() - 1, firstLineStart);
List<JSDocTag> tags = new ArrayList<>();
for (JSDocTagParser.Tag tag : r.snd()) {
String title = tag.title;
@@ -269,21 +268,21 @@ public class JSDocParser {
return new SourceLocation(pos());
}
/**
* Returns the absolute position of the start of the current token.
*/
/** Returns the absolute position of the start of the current token. */
private Position pos() {
return new Position(this.lineNumber + 1, startOfCurToken - lineStart, startOfCurToken + absoluteOffset);
return new Position(
this.lineNumber + 1, startOfCurToken - lineStart, startOfCurToken + absoluteOffset);
}
/**
* Returns the absolute position of the end of the previous token.
*
* This can differ from the start of the current token in case the two tokens
* are separated by whitespace.
* <p>This can differ from the start of the current token in case the two tokens are separated
* by whitespace.
*/
private Position endPos() {
return new Position(this.lineNumber + 1, endOfPrevToken - lineStart, endOfPrevToken + absoluteOffset);
return new Position(
this.lineNumber + 1, endOfPrevToken - lineStart, endOfPrevToken + absoluteOffset);
}
private <T extends JSDocTypeExpression> T finishNode(T node) {
@@ -306,7 +305,8 @@ public class JSDocParser {
if (index >= source.length()) return -1;
int ch = source.charAt(index);
++index;
if (isLineTerminator(ch) && !(ch == '\r' && index < endIndex && source.charAt(index) == '\n')) {
if (isLineTerminator(ch)
&& !(ch == '\r' && index < endIndex && source.charAt(index) == '\n')) {
lineNumber += 1;
lineStart = index;
index = skipStars(index, endIndex);
@@ -950,7 +950,10 @@ public class JSDocParser {
consume(Token.COLON);
expr =
finishNode(
new ParameterType(new SourceLocation(loc), ((NameExpression) expr).getName(), parseTypeExpression()));
new ParameterType(
new SourceLocation(loc),
((NameExpression) expr).getName(),
parseTypeExpression()));
}
if (token == Token.EQUAL) {
consume(Token.EQUAL);
@@ -1125,8 +1128,7 @@ public class JSDocParser {
consume(Token.RBRACK, "expected an array-style type declaration (' + value + '[])");
List<JSDocTypeExpression> expressions = new ArrayList<>();
expressions.add(expr);
NameExpression nameExpr =
finishNode(new NameExpression(new SourceLocation(loc), "Array"));
NameExpression nameExpr = finishNode(new NameExpression(new SourceLocation(loc), "Array"));
return finishNode(new TypeApplication(loc, nameExpr, expressions));
}
@@ -1183,7 +1185,8 @@ public class JSDocParser {
return expr;
}
private JSDocTypeExpression parseType(int startIndex, int endIndex, int lineStart, int lineNumber) throws ParseError {
private JSDocTypeExpression parseType(
int startIndex, int endIndex, int lineStart, int lineNumber) throws ParseError {
JSDocTypeExpression expr;
this.lineNumber = lineNumber;
@@ -1202,7 +1205,8 @@ public class JSDocParser {
return expr;
}
private JSDocTypeExpression parseParamType(int startIndex, int endIndex, int lineStart, int lineNumber) throws ParseError {
private JSDocTypeExpression parseParamType(
int startIndex, int endIndex, int lineStart, int lineNumber) throws ParseError {
JSDocTypeExpression expr;
this.lineNumber = lineNumber;

View File

@@ -3,6 +3,8 @@ package com.semmle.js.parser;
import com.semmle.js.ast.Comment;
import com.semmle.js.ast.Node;
import com.semmle.js.ast.Token;
import com.semmle.js.extractor.ExtractionMetrics;
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
import com.semmle.js.extractor.ExtractorConfig;
import com.semmle.js.extractor.ExtractorConfig.SourceType;
import java.util.List;
@@ -65,7 +67,11 @@ public class JSParser {
}
}
public static Result parse(ExtractorConfig config, SourceType sourceType, String source) {
return JcornWrapper.parse(config, sourceType, source);
public static Result parse(
ExtractorConfig config, SourceType sourceType, String source, ExtractionMetrics metrics) {
metrics.startPhase(ExtractionPhase.JSParser_parse);
Result result = JcornWrapper.parse(config, sourceType, source);
metrics.stopPhase(ExtractionPhase.JSParser_parse);
return result;
}
}

View File

@@ -1,13 +1,5 @@
package com.semmle.js.parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
@@ -149,6 +141,13 @@ import com.semmle.ts.ast.TypeofTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.UnionTypeExpr;
import com.semmle.util.collections.CollectionUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class for converting a <a
@@ -2035,7 +2034,9 @@ public class TypeScriptASTConverter {
private Node convertTaggedTemplateExpression(JsonObject node, SourceLocation loc)
throws ParseError {
return new TaggedTemplateExpression(
loc, convertChild(node, "tag"), convertChild(node, "template"),
loc,
convertChild(node, "tag"),
convertChild(node, "template"),
convertChildrenAsTypes(node, "typeArguments"));
}

View File

@@ -1,5 +1,27 @@
package com.semmle.js.parser;
import ch.qos.logback.classic.Level;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.semmle.js.extractor.ExtractionMetrics;
import com.semmle.js.parser.JSParser.Result;
import com.semmle.ts.extractor.TypeTable;
import com.semmle.util.data.StringUtil;
import com.semmle.util.data.UnitParser;
import com.semmle.util.exception.CatastrophicError;
import com.semmle.util.exception.Exceptions;
import com.semmle.util.exception.InterruptedError;
import com.semmle.util.exception.ResourceError;
import com.semmle.util.exception.UserError;
import com.semmle.util.io.WholeIO;
import com.semmle.util.logging.LogbackUtils;
import com.semmle.util.process.AbstractProcessBuilder;
import com.semmle.util.process.Builder;
import com.semmle.util.process.Env;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
@@ -16,40 +38,17 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.semmle.js.parser.JSParser.Result;
import com.semmle.ts.extractor.TypeTable;
import com.semmle.util.data.StringUtil;
import com.semmle.util.data.UnitParser;
import com.semmle.util.exception.CatastrophicError;
import com.semmle.util.exception.Exceptions;
import com.semmle.util.exception.InterruptedError;
import com.semmle.util.exception.ResourceError;
import com.semmle.util.exception.UserError;
import com.semmle.util.io.WholeIO;
import com.semmle.util.logging.LogbackUtils;
import com.semmle.util.process.AbstractProcessBuilder;
import com.semmle.util.process.Builder;
import com.semmle.util.process.Env;
import ch.qos.logback.classic.Level;
/**
* The Java half of our wrapper for invoking the TypeScript parser.
*
* <p>The Node.js half of the wrapper is expected to live at {@code
* $SEMMLE_DIST/tools/typescript-parser-wrapper/main.js}; non-standard locations can be configured
* using the property {@value #PARSER_WRAPPER_PATH_ENV_VAR}.
*
* <p>The script launches the Node.js wrapper in the Node.js runtime, looking for {@code node}
* on the {@code PATH} by default. Non-standard locations can be configured using the property
* {@value #TYPESCRIPT_NODE_RUNTIME_VAR}, and additional arguments can be configured using the
* property {@value #TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS_VAR}.
*
* <p>The script launches the Node.js wrapper in the Node.js runtime, looking for {@code node} on
* the {@code PATH} by default. Non-standard locations can be configured using the property {@value
* #TYPESCRIPT_NODE_RUNTIME_VAR}, and additional arguments can be configured using the property
* {@value #TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS_VAR}.
*
* <p>The script is started upon parsing the first TypeScript file and then is kept running in the
* background, passing it requests for parsing files and getting JSON-encoded ASTs as responses.
@@ -62,8 +61,8 @@ public class TypeScriptParser {
public static final String PARSER_WRAPPER_PATH_ENV_VAR = "SEMMLE_TYPESCRIPT_PARSER_WRAPPER";
/**
* An environment variable that can be set to indicate the location of the Node.js runtime,
* as an alternative to adding Node to the PATH.
* An environment variable that can be set to indicate the location of the Node.js runtime, as an
* alternative to adding Node to the PATH.
*/
public static final String TYPESCRIPT_NODE_RUNTIME_VAR = "SEMMLE_TYPESCRIPT_NODE_RUNTIME";
@@ -71,7 +70,8 @@ public class TypeScriptParser {
* An environment variable that can be set to provide additional arguments to the Node.js runtime
* each time it is invoked. Arguments should be separated by spaces.
*/
public static final String TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS_VAR = "SEMMLE_TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS";
public static final String TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS_VAR =
"SEMMLE_TYPESCRIPT_NODE_RUNTIME_EXTRA_ARGS";
/**
* An environment variable that can be set to specify a timeout to use when verifying the
@@ -102,8 +102,8 @@ public class TypeScriptParser {
/**
* An environment variable with additional VM arguments to pass to the Node process.
* <p>
* Only <code>--inspect</code> or <code>--inspect-brk</code> may be used at the moment.
*
* <p>Only <code>--inspect</code> or <code>--inspect-brk</code> may be used at the moment.
*/
public static final String TYPESCRIPT_NODE_FLAGS = "SEMMLE_TYPESCRIPT_NODE_FLAGS";
@@ -123,8 +123,8 @@ public class TypeScriptParser {
private String nodeJsRuntime;
/**
* Arguments to pass to the Node.js runtime each time it is invoked.
* Initialised by {@link #verifyNodeInstallation}.
* Arguments to pass to the Node.js runtime each time it is invoked. Initialised by {@link
* #verifyNodeInstallation}.
*/
private List<String> nodeJsRuntimeExtraArgs = Collections.emptyList();
@@ -139,7 +139,8 @@ public class TypeScriptParser {
/**
* Verifies that Node.js and TypeScript are installed and throws an exception otherwise.
*
* @param verbose if true, log the Node.js executable path, version strings, and any additional arguments.
* @param verbose if true, log the Node.js executable path, version strings, and any additional
* arguments.
*/
public void verifyInstallation(boolean verbose) {
verifyNodeInstallation();
@@ -158,7 +159,7 @@ public class TypeScriptParser {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
// Determine where to find the Node.js runtime.
String explicitNodeJsRuntime = Env.systemEnv().get(TYPESCRIPT_NODE_RUNTIME_VAR);
if (explicitNodeJsRuntime != null) {
@@ -175,7 +176,9 @@ public class TypeScriptParser {
nodeJsRuntimeExtraArgs = Arrays.asList(extraArgs.split("\\s+"));
}
Builder b = new Builder(getNodeJsRuntimeInvocation("--version"), out, err, getParserWrapper().getParentFile());
Builder b =
new Builder(
getNodeJsRuntimeInvocation("--version"), out, err, getParserWrapper().getParentFile());
b.expectFailure(); // We want to do our own logging in case of an error.
int timeout = Env.systemEnv().getInt(TYPESCRIPT_TIMEOUT_VAR, 10000);
@@ -203,15 +206,14 @@ public class TypeScriptParser {
}
/**
* Gets a command line to invoke the Node.js runtime.
* Any arguments in {@link TypeScriptParser#nodeJsRuntimeExtraArgs}
* are passed first, followed by those in {@code args}.
* Gets a command line to invoke the Node.js runtime. Any arguments in {@link
* TypeScriptParser#nodeJsRuntimeExtraArgs} are passed first, followed by those in {@code args}.
*/
private List<String> getNodeJsRuntimeInvocation(String ...args) {
private List<String> getNodeJsRuntimeInvocation(String... args) {
List<String> result = new ArrayList<>();
result.add(nodeJsRuntime);
result.addAll(nodeJsRuntimeExtraArgs);
for(String arg : args) {
for (String arg : args) {
result.add(arg);
}
return result;
@@ -361,17 +363,22 @@ public class TypeScriptParser {
*
* <p>If the file is not part of a project, only syntactic information will be extracted.
*/
public Result parse(File sourceFile, String source) {
public Result parse(File sourceFile, String source, ExtractionMetrics metrics) {
JsonObject request = new JsonObject();
request.add("command", new JsonPrimitive("parse"));
request.add("filename", new JsonPrimitive(sourceFile.getAbsolutePath()));
metrics.startPhase(ExtractionMetrics.ExtractionPhase.TypeScriptParser_talkToParserWrapper);
JsonObject response = talkToParserWrapper(request);
metrics.stopPhase(ExtractionMetrics.ExtractionPhase.TypeScriptParser_talkToParserWrapper);
try {
checkResponseType(response, "ast");
JsonObject nodeFlags = response.get("nodeFlags").getAsJsonObject();
JsonObject syntaxKinds = response.get("syntaxKinds").getAsJsonObject();
JsonObject ast = response.get("ast").getAsJsonObject();
return new TypeScriptASTConverter(nodeFlags, syntaxKinds).convertAST(ast, source);
metrics.startPhase(ExtractionMetrics.ExtractionPhase.TypeScriptASTConverter_convertAST);
Result converted = new TypeScriptASTConverter(nodeFlags, syntaxKinds).convertAST(ast, source);
metrics.stopPhase(ExtractionMetrics.ExtractionPhase.TypeScriptASTConverter_convertAST);
return converted;
} catch (IllegalStateException e) {
throw new CatastrophicError(
"TypeScript parser wrapper sent unexpected response: " + response, e);

View File

@@ -6,7 +6,7 @@ import com.semmle.js.ast.Visitor;
/**
* A unary operator applied to a type.
*
* This can be <tt>keyof T</tt> or <tt>readonly T</tt>.
* <p>This can be <tt>keyof T</tt> or <tt>readonly T</tt>.
*/
public class UnaryTypeExpr extends TypeExpression {
private final ITypeExpression elementType;

View File

@@ -4,7 +4,6 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.semmle.util.trap.TrapWriter;
import com.semmle.util.trap.TrapWriter.Label;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

View File

@@ -0,0 +1,25 @@
/**
* @name Extraction metrics file data
* @description Extraction metrics and related information for profiling the extraction of individual files.
* @kind table
* @id js/meta/extraction/file-data
* @tags meta
*/
import javascript
import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics
FileWithExtractionMetrics getACacheMember(string cacheFile) { cacheFile = result.getCacheFile() }
FileWithExtractionMetrics getACacheHit(FileWithExtractionMetrics f) {
result = getACacheMember(f.getCacheFile()) and
result.isFromCache()
}
from FileWithExtractionMetrics file, boolean fromCache
where (if file.isFromCache() then fromCache = true else fromCache = false)
select file.getAbsolutePath() as FILE, file.getCpuTime() as CPU_NANO,
file.getNumberOfLines() as LINES, count(Locatable n | n.getFile() = file) as LOCATABLES,
count(TypeAnnotation n | n.getFile() = file) as TYPES, file.getLength() as LENGTH,
fromCache as FROM_CACHE, count(getACacheMember(file.getCacheFile())) as CACHE_MEMBERS,
count(getACacheHit(file)) as CACHE_HITS, file.getCacheFile() as CACHE_FILE

View File

@@ -0,0 +1,18 @@
/**
* @name File with missing extraction metrics
* @description A file missing extraction metrics is indicative of a faulty extractor.
* @kind problem
* @problem.severity warning
* @precision low
* @id js/meta/extraction/missing-metrics
* @tags meta
*/
import javascript
import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics
from File f, string cause
where not extraction_data(f, _, _, _) and cause = "No extraction_data for this file"
or
not extraction_time(f, _,_, _) and cause = "No extraction_time for this file"
select f, cause

View File

@@ -0,0 +1,12 @@
/**
* @name Extractor phase timings
* @description An overview of how time was spent during extraction
* @kind table
* @id js/meta/extraction/phase-timings
* @tags meta
*/
import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics
from PhaseName phaseName
select phaseName, Aggregated::getCpuTime(phaseName) as CPU_NANO, Aggregated::getWallclockTime(phaseName) as WALLCLOCK_NANO

View File

@@ -0,0 +1,130 @@
import javascript
/**
* INTERNAL: Do not use in ordinary queries.
*
* Extraction metrics for profiling extraction behaviours.
*/
module ExtractionMetrics {
/**
* A file with extraction metrics.
*/
class FileWithExtractionMetrics extends File {
FileWithExtractionMetrics() {
extraction_data(this, _, _, _) and extraction_time(this, _, _, _)
}
/**
* Gets the CPU time in nanoseconds it took to extract this file.
*/
float getCpuTime() { result = strictsum(getTime(_, 0)) }
/**
* Gets the wall-clock time in nanoseconds it took to extract this file.
*/
float getWallclockTime() { result = strictsum(getTime(_, 1)) }
/**
* Gets the CPU time in nanoseconds it took to process phase `phaseName` during the extraction of this file.
*/
float getCpuTime(PhaseName phaseName) { result = getTime(phaseName, 0) }
/**
* Gets the wall-clock time in nanoseconds it took to process phase `phaseName` during the extraction of this file.
*/
float getWallclockTime(PhaseName phaseName) { result = getTime(phaseName, 1) }
/**
* Holds if this file was extracted from the trap cache.
*/
predicate isFromCache() { extraction_data(this, _, true, _) }
/**
* Gets the path to the cache file used for extraction of this file.
*/
string getCacheFile() { extraction_data(this, result, _, _) }
/**
* Gets the number of UTF16 code units in this file.
*/
int getLength() { extraction_data(this, _, _, result) }
private float getTime(PhaseName phaseName, int timerKind) {
exists(float time |
// note that we use strictsum to make it clear if data is missing because it comes from an upgraded database.
strictsum(int phaseId, float r |
phaseName = getExtractionPhaseName(phaseId) and
extraction_time(this, phaseId, timerKind, r)
|
r
) = time
|
// assume the cache-lookup was for free
if isFromCache() then result = 0 else result = time
)
}
}
/**
* Converts database ids to human-readable names.
*/
private string getExtractionPhaseName(int phaseId) {
// these names ought to match the names used in
// `com.semmle.js.extractor.ExtractionTimer.ExtractionPhase`
"ASTExtractor_extract" = result and 0 = phaseId
or
"CFGExtractor_extract" = result and 1 = phaseId
or
"FileExtractor_extractContents" = result and 2 = phaseId
or
"JSExtractor_extract" = result and 3 = phaseId
or
"JSParser_parse" = result and 4 = phaseId
or
"LexicalExtractor_extractLines" = result and 5 = phaseId
or
"LexicalExtractor_extractTokens" = result and 6 = phaseId
or
"TypeScriptASTConverter_convertAST" = result and 7 = phaseId
or
"TypeScriptParser_talkToParserWrapper" = result and 8 = phaseId
}
/**
* The name of a phase of the extraction.
*/
class PhaseName extends string {
PhaseName() { this = getExtractionPhaseName(_) }
}
/**
* Utilities for aggregating metrics for multiple files.
*/
module Aggregated {
/**
* Gets the total CPU time spent on extraction.
*/
float getCpuTime() { result = strictsum(any(FileWithExtractionMetrics f).getCpuTime()) }
/**
* Gets the total wallclock time spent on extraction.
*/
float getWallclockTime() {
result = strictsum(any(FileWithExtractionMetrics f).getWallclockTime())
}
/**
* Gets the total CPU time spent in phase `phaseName` of the extraction.
*/
float getCpuTime(PhaseName phaseName) {
result = strictsum(any(FileWithExtractionMetrics f).getCpuTime(phaseName))
}
/**
* Gets the total wallclock time spent in phase `phaseName` of the extraction.
*/
float getWallclockTime(PhaseName phaseName) {
result = strictsum(any(FileWithExtractionMetrics f).getWallclockTime(phaseName))
}
}
}

View File

@@ -1136,3 +1136,32 @@ configLocations(
);
@configLocatable = @config | @configName | @configValue;
/**
* The time taken for the extraction of a file.
* This table contains non-deterministic content.
*
* The sum of the `time` column for each (`file`, `timerKind`) pair
* is the total time taken for extraction of `file`. The `extractionPhase`
* column provides a granular view of the extraction time of the file.
*/
extraction_time(
int file : @file ref,
// see `com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase`.
int extractionPhase: int ref,
// 0 for the elapsed CPU time in nanoseconds, 1 for the elapsed wallclock time in nanoseconds
int timerKind: int ref,
float time: float ref
)
/**
* Non-timing related data for the extraction of a single file.
* This table contains non-deterministic content.
*/
extraction_data(
int file : @file ref,
// the absolute path to the cache file
varchar(900) cacheFile: string ref,
boolean fromCache: boolean ref,
int length: int ref
)

View File

@@ -30782,5 +30782,457 @@
</dep>
</dependencies>
</relation>
<relation>
<name>extraction_time</name>
<cardinality>378</cardinality>
<columnsizes>
<e>
<k>file</k>
<v>21</v>
</e>
<e>
<k>extractionPhase</k>
<v>9</v>
</e>
<e>
<k>timerKind</k>
<v>2</v>
</e>
<e>
<k>time</k>
<v>43</v>
</e>
</columnsizes>
<dependencies>
<dep>
<src>file</src>
<trg>extractionPhase</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>9</a>
<b>10</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>file</src>
<trg>timerKind</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>2</a>
<b>3</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>file</src>
<trg>time</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>3</a>
<b>4</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>extractionPhase</src>
<trg>file</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>21</a>
<b>22</b>
<v>9</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>extractionPhase</src>
<trg>timerKind</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>2</a>
<b>3</b>
<v>9</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>extractionPhase</src>
<trg>time</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>8</v>
</b>
<b>
<a>42</a>
<b>43</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>timerKind</src>
<trg>file</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>21</a>
<b>22</b>
<v>2</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>timerKind</src>
<trg>extractionPhase</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>9</a>
<b>10</b>
<v>2</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>timerKind</src>
<trg>time</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>22</a>
<b>23</b>
<v>2</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>time</src>
<trg>file</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>42</v>
</b>
<b>
<a>21</a>
<b>22</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>time</src>
<trg>extractionPhase</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>42</v>
</b>
<b>
<a>8</a>
<b>9</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>time</src>
<trg>timerKind</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>42</v>
</b>
<b>
<a>2</a>
<b>3</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
</dependencies>
</relation>
<relation>
<name>extraction_data</name>
<cardinality>21</cardinality>
<columnsizes>
<e>
<k>file</k>
<v>21</v>
</e>
<e>
<k>cacheFile</k>
<v>21</v>
</e>
<e>
<k>fromCache</k>
<v>1</v>
</e>
<e>
<k>length</k>
<v>21</v>
</e>
</columnsizes>
<dependencies>
<dep>
<src>file</src>
<trg>cacheFile</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>file</src>
<trg>fromCache</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>file</src>
<trg>length</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>cacheFile</src>
<trg>file</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>cacheFile</src>
<trg>fromCache</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>cacheFile</src>
<trg>length</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>fromCache</src>
<trg>file</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>21</a>
<b>22</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>fromCache</src>
<trg>cacheFile</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>21</a>
<b>22</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>fromCache</src>
<trg>length</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>21</a>
<b>22</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>length</src>
<trg>file</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>length</src>
<trg>cacheFile</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>length</src>
<trg>fromCache</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>21</v>
</b>
</bs>
</hist>
</val>
</dep>
</dependencies>
</relation>
</stats>
</dbstats>

View File

@@ -0,0 +1,4 @@
| tst2.js:0:0:0:0 | tst2.js |
| tst.html:0:0:0:0 | tst.html |
| tst.js:0:0:0:0 | tst.js |
| tst.ts:0:0:0:0 | tst.ts |

View File

@@ -0,0 +1,4 @@
import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics
from FileWithExtractionMetrics f
select f

View File

@@ -0,0 +1,4 @@
| tst2.js:0:0:0:0 | tst2.js | 26 |
| tst.html:0:0:0:0 | tst.html | 127 |
| tst.js:0:0:0:0 | tst.js | 26 |
| tst.ts:0:0:0:0 | tst.ts | 31 |

View File

@@ -0,0 +1,4 @@
import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics
from FileWithExtractionMetrics f
select f, f.getLength()

View File

@@ -0,0 +1,36 @@
| tst2.js:0:0:0:0 | tst2.js | ASTExtractor_extract |
| tst2.js:0:0:0:0 | tst2.js | CFGExtractor_extract |
| tst2.js:0:0:0:0 | tst2.js | FileExtractor_extractContents |
| tst2.js:0:0:0:0 | tst2.js | JSExtractor_extract |
| tst2.js:0:0:0:0 | tst2.js | JSParser_parse |
| tst2.js:0:0:0:0 | tst2.js | LexicalExtractor_extractLines |
| tst2.js:0:0:0:0 | tst2.js | LexicalExtractor_extractTokens |
| tst2.js:0:0:0:0 | tst2.js | TypeScriptASTConverter_convertAST |
| tst2.js:0:0:0:0 | tst2.js | TypeScriptParser_talkToParserWrapper |
| tst.html:0:0:0:0 | tst.html | ASTExtractor_extract |
| tst.html:0:0:0:0 | tst.html | CFGExtractor_extract |
| tst.html:0:0:0:0 | tst.html | FileExtractor_extractContents |
| tst.html:0:0:0:0 | tst.html | JSExtractor_extract |
| tst.html:0:0:0:0 | tst.html | JSParser_parse |
| tst.html:0:0:0:0 | tst.html | LexicalExtractor_extractLines |
| tst.html:0:0:0:0 | tst.html | LexicalExtractor_extractTokens |
| tst.html:0:0:0:0 | tst.html | TypeScriptASTConverter_convertAST |
| tst.html:0:0:0:0 | tst.html | TypeScriptParser_talkToParserWrapper |
| tst.js:0:0:0:0 | tst.js | ASTExtractor_extract |
| tst.js:0:0:0:0 | tst.js | CFGExtractor_extract |
| tst.js:0:0:0:0 | tst.js | FileExtractor_extractContents |
| tst.js:0:0:0:0 | tst.js | JSExtractor_extract |
| tst.js:0:0:0:0 | tst.js | JSParser_parse |
| tst.js:0:0:0:0 | tst.js | LexicalExtractor_extractLines |
| tst.js:0:0:0:0 | tst.js | LexicalExtractor_extractTokens |
| tst.js:0:0:0:0 | tst.js | TypeScriptASTConverter_convertAST |
| tst.js:0:0:0:0 | tst.js | TypeScriptParser_talkToParserWrapper |
| tst.ts:0:0:0:0 | tst.ts | ASTExtractor_extract |
| tst.ts:0:0:0:0 | tst.ts | CFGExtractor_extract |
| tst.ts:0:0:0:0 | tst.ts | FileExtractor_extractContents |
| tst.ts:0:0:0:0 | tst.ts | JSExtractor_extract |
| tst.ts:0:0:0:0 | tst.ts | JSParser_parse |
| tst.ts:0:0:0:0 | tst.ts | LexicalExtractor_extractLines |
| tst.ts:0:0:0:0 | tst.ts | LexicalExtractor_extractTokens |
| tst.ts:0:0:0:0 | tst.ts | TypeScriptASTConverter_convertAST |
| tst.ts:0:0:0:0 | tst.ts | TypeScriptParser_talkToParserWrapper |

View File

@@ -0,0 +1,7 @@
import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics
from FileWithExtractionMetrics f, PhaseName phase
where
exists(f.getCpuTime(phase)) and
exists(f.getWallclockTime(phase))
select f, phase

View File

@@ -0,0 +1,7 @@
<script>
console.log("extract me")
</script>
<script>
console.log("extract me")
</script>
<script src="./tst.js"></script>

View File

@@ -0,0 +1 @@
console.log("extract me")

View File

@@ -0,0 +1 @@
console.log("extract me too");

View File

@@ -0,0 +1 @@
console.log("extract me")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: adds extraction metrics tables
compatibility: backwards