sclass CodeSafetyChecker { // general data SS identifierSafetyMap; bool identifiersStartingWithDollarAreSafe; // data for code currently tested S code; Set identifiers; LS unknownIdentifiers; LS tags; void init { if (identifierSafetyMap == null) identifierSafetyMap = codeAnalysis_identifierSafetyMap(); } LS tokenize(S code) { ret javaTok(code); } run { init(); LS tok = tokenize(code); tok_inStringEvals(tok); identifiers = tok_allIdentifiersWithoutColonEquals(tok); unknownIdentifiers = new L; Set tags = treeSet(); for (S id : identifiers) { S tag; if (identifiersStartingWithDollarAreSafe && startsWith(id, "$")) tag = "safe"; else tag = or2(mapGet(identifierSafetyMap, id), "?"); if (eq(tag, "?")) unknownIdentifiers.add(id); tags.addAll(tokSplitAtComma(tag)); } this.tags = simplifySafetyTags(tags); if (empty(this.tags)) this.tags.add("safe"); } S checkCode(S code) { this.code = code; run(); ret joinWithComma(tags); } S verbalCheckResult(S code) { checkCode(code); ret verbalCheckResult(); } S verbalCheckResult() { S safety = joinWithComma(tags); if (neq(safety, "safe") && nempty(unknownIdentifiers)) safety += ". Unknown identifiers: " + joinWithComma(unknownIdentifiers); ret safety; } void markSafe(S... ids) { init(); putMultipleKeys(identifierSafetyMap, asList(ids), "safe"); } bool isSafe() { ret eq(tags, ll("safe")); } }