559 lines
21 KiB
Java
559 lines
21 KiB
Java
import java.util.*;
|
|
import java.util.concurrent.*;
|
|
import java.util.concurrent.atomic.*;
|
|
|
|
/**
|
|
* CWE-407 simulation for Kdenlive defects.
|
|
* kdenlive-0001: ThumbnailCache storedOnDisk vector linear find for dedup
|
|
* kdenlive-0002: TimelineModel clipIds vector linear find in mix loop
|
|
* kdenlive-0003: TimelineController sorted_clips vector linear find in moveGroup
|
|
* kdenlive-0004: TimelineModel all_items list linear find in resize
|
|
* kdenlive-0005: PreviewManager m_renderedChunks/m_dirtyChunks QVariantList linear contains
|
|
* kdenlive-0006: TimelineController canceled guides vector linear find
|
|
* kdenlive-0007: AssetParameterModel m_rows indexOf in parameter loops
|
|
* kdenlive-0008: UrlListParamWidget addItemsInSameFolder linear find on map values
|
|
* kdenlive-0009: MOAD-0005 m_lumacache QMap unprotected concurrent write from QtConcurrent worker
|
|
* kdenlive-0010: KeyframeModelList::checkConsistency() QList<GenTime>::contains() O(P*K^2)
|
|
*/
|
|
public class KdenliveTest {
|
|
|
|
// --- kdenlive-0001: ThumbnailCache storedOnDisk dedup ---
|
|
|
|
static long thumbnailCacheDefect(int numClips, int framesPerClip) {
|
|
// Simulates m_storedOnDisk as Map<String, List<Integer>>
|
|
Map<String, List<Integer>> storedOnDisk = new HashMap<>();
|
|
long ops = 0;
|
|
for (int c = 0; c < numClips; c++) {
|
|
String binId = "clip-" + c;
|
|
storedOnDisk.put(binId, new ArrayList<>());
|
|
for (int f = 0; f < framesPerClip; f++) {
|
|
List<Integer> vec = storedOnDisk.get(binId);
|
|
// std::find linear scan before push_back
|
|
boolean found = false;
|
|
for (int existing : vec) {
|
|
ops++;
|
|
if (existing == f) { found = true; break; }
|
|
}
|
|
if (!found) {
|
|
vec.add(f);
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long thumbnailCacheFixed(int numClips, int framesPerClip) {
|
|
Map<String, Set<Integer>> storedOnDisk = new HashMap<>();
|
|
long ops = 0;
|
|
for (int c = 0; c < numClips; c++) {
|
|
String binId = "clip-" + c;
|
|
storedOnDisk.put(binId, new HashSet<>());
|
|
for (int f = 0; f < framesPerClip; f++) {
|
|
ops++;
|
|
storedOnDisk.get(binId).add(f);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- kdenlive-0002: clipIds mix membership ---
|
|
|
|
static long clipIdsMixDefect(int N) {
|
|
List<Integer> clipIds = new ArrayList<>();
|
|
for (int i = 0; i < N; i++) clipIds.add(i);
|
|
long ops = 0;
|
|
for (int s : clipIds) {
|
|
// Simulate 3 std::find calls per clip (typical in mix logic)
|
|
for (int check = 0; check < 3; check++) {
|
|
int target = (s + 1) % N;
|
|
for (int id : clipIds) {
|
|
ops++;
|
|
if (id == target) break;
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long clipIdsMixFixed(int N) {
|
|
List<Integer> clipIds = new ArrayList<>();
|
|
Set<Integer> clipIdSet = new HashSet<>();
|
|
for (int i = 0; i < N; i++) { clipIds.add(i); clipIdSet.add(i); }
|
|
long ops = 0;
|
|
for (int s : clipIds) {
|
|
for (int check = 0; check < 3; check++) {
|
|
ops++;
|
|
int target = (s + 1) % N;
|
|
clipIdSet.contains(target);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- kdenlive-0003: sorted_clips moveGroup membership ---
|
|
|
|
static long sortedClipsMoveDefect(int N) {
|
|
List<Integer> sortedClips = new ArrayList<>();
|
|
for (int i = 0; i < N; i++) sortedClips.add(i);
|
|
long ops = 0;
|
|
for (int item : sortedClips) {
|
|
// 2 std::find calls per clip (startMix + endMix check)
|
|
for (int check = 0; check < 2; check++) {
|
|
int target = (item + 1) % N;
|
|
for (int id : sortedClips) {
|
|
ops++;
|
|
if (id == target) break;
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long sortedClipsMoveFixed(int N) {
|
|
List<Integer> sortedClips = new ArrayList<>();
|
|
Set<Integer> sortedSet = new HashSet<>();
|
|
for (int i = 0; i < N; i++) { sortedClips.add(i); sortedSet.add(i); }
|
|
long ops = 0;
|
|
for (int item : sortedClips) {
|
|
for (int check = 0; check < 2; check++) {
|
|
ops++;
|
|
sortedSet.contains((item + 1) % N);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- kdenlive-0004: all_items resize dedup ---
|
|
|
|
static long allItemsResizeDefect(int N) {
|
|
// currentSelection has N items; all_items grows as we add non-duplicates
|
|
LinkedList<Integer> allItems = new LinkedList<>();
|
|
allItems.add(0); // itemId
|
|
long ops = 0;
|
|
for (int id = 1; id < N; id++) {
|
|
// std::find on all_items
|
|
boolean found = false;
|
|
for (int existing : allItems) {
|
|
ops++;
|
|
if (existing == id) { found = true; break; }
|
|
}
|
|
if (!found) {
|
|
allItems.add(id);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long allItemsResizeFixed(int N) {
|
|
LinkedList<Integer> allItems = new LinkedList<>();
|
|
Set<Integer> allItemsSet = new HashSet<>();
|
|
allItems.add(0);
|
|
allItemsSet.add(0);
|
|
long ops = 0;
|
|
for (int id = 1; id < N; id++) {
|
|
ops++;
|
|
if (!allItemsSet.contains(id)) {
|
|
allItems.add(id);
|
|
allItemsSet.add(id);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- kdenlive-0005: PreviewManager chunk lists linear contains ---
|
|
|
|
static long previewChunksDefect(int numDirty, int numNew) {
|
|
// Simulates m_dirtyChunks as QVariantList with .contains() dedup
|
|
List<Integer> dirtyChunks = new ArrayList<>();
|
|
for (int i = 0; i < numDirty; i++) dirtyChunks.add(i * 25);
|
|
List<Integer> renderedChunks = new ArrayList<>();
|
|
for (int i = 0; i < numDirty / 2; i++) renderedChunks.add(i * 25);
|
|
long ops = 0;
|
|
// reloadChunks pattern: loop over new chunks, .contains() on existing
|
|
for (int i = 0; i < numNew; i++) {
|
|
int frame = i * 25;
|
|
// m_dirtyChunks.contains(frame) - linear scan
|
|
for (int existing : dirtyChunks) {
|
|
ops++;
|
|
if (existing == frame) break;
|
|
}
|
|
// m_renderedChunks.contains(frame) - linear scan
|
|
for (int existing : renderedChunks) {
|
|
ops++;
|
|
if (existing == frame) break;
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long previewChunksFixed(int numDirty, int numNew) {
|
|
Set<Integer> dirtySet = new HashSet<>();
|
|
for (int i = 0; i < numDirty; i++) dirtySet.add(i * 25);
|
|
Set<Integer> renderedSet = new HashSet<>();
|
|
for (int i = 0; i < numDirty / 2; i++) renderedSet.add(i * 25);
|
|
long ops = 0;
|
|
for (int i = 0; i < numNew; i++) {
|
|
int frame = i * 25;
|
|
ops++; // O(1) HashSet.contains for dirty
|
|
dirtySet.contains(frame);
|
|
ops++; // O(1) HashSet.contains for rendered
|
|
renderedSet.contains(frame);
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- kdenlive-0006: canceled guide filter ---
|
|
|
|
static long canceledGuidesDefect(int numGuides, int numCanceled) {
|
|
List<Integer> guides = new ArrayList<>();
|
|
for (int i = 0; i < numGuides; i++) guides.add(i * 30);
|
|
List<Integer> canceled = new ArrayList<>();
|
|
for (int i = 0; i < numCanceled; i++) canceled.add(i * 60); // every other guide
|
|
long ops = 0;
|
|
for (int guidePos : guides) {
|
|
// std::find on canceled vector
|
|
for (int c : canceled) {
|
|
ops++;
|
|
if (c == guidePos) break;
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long canceledGuidesFixed(int numGuides, int numCanceled) {
|
|
List<Integer> guides = new ArrayList<>();
|
|
for (int i = 0; i < numGuides; i++) guides.add(i * 30);
|
|
Set<Integer> canceledSet = new HashSet<>();
|
|
for (int i = 0; i < numCanceled; i++) canceledSet.add(i * 60);
|
|
long ops = 0;
|
|
for (int guidePos : guides) {
|
|
ops++; // O(1) HashSet lookup
|
|
canceledSet.contains(guidePos);
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- kdenlive-0007: AssetParameterModel m_rows indexOf in loops ---
|
|
|
|
static long rowsIndexOfDefect(int numParams) {
|
|
// m_rows is QVector<QString>, indexOf called per param
|
|
List<String> rows = new ArrayList<>();
|
|
for (int i = 0; i < numParams; i++) rows.add("param_" + i);
|
|
long ops = 0;
|
|
// Simulates getAllParameters/toJson loop
|
|
for (int i = 0; i < numParams; i++) {
|
|
String name = "param_" + i;
|
|
// indexOf: linear scan of m_rows
|
|
for (int j = 0; j < rows.size(); j++) {
|
|
ops++;
|
|
if (rows.get(j).equals(name)) break;
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long rowsIndexOfFixed(int numParams) {
|
|
Map<String, Integer> rowIndex = new HashMap<>();
|
|
for (int i = 0; i < numParams; i++) rowIndex.put("param_" + i, i);
|
|
long ops = 0;
|
|
for (int i = 0; i < numParams; i++) {
|
|
ops++; // O(1) HashMap lookup
|
|
rowIndex.get("param_" + i);
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// --- kdenlive-0008: UrlListParamWidget addItemsInSameFolder ---
|
|
|
|
static long urlListFindDefect(int numEntries, int numExisting) {
|
|
// QMap values iterated with std::find for each directory entry
|
|
Map<String, String> listValues = new LinkedHashMap<>();
|
|
for (int i = 0; i < numExisting; i++) {
|
|
listValues.put("file_" + i, "/path/to/file_" + i + ".png");
|
|
}
|
|
long ops = 0;
|
|
for (int i = 0; i < numEntries; i++) {
|
|
String path = "/dir/entry_" + i + ".png";
|
|
// std::find iterates all map values
|
|
for (String val : listValues.values()) {
|
|
ops++;
|
|
if (val.equals(path)) break;
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long urlListFindFixed(int numEntries, int numExisting) {
|
|
Map<String, String> listValues = new LinkedHashMap<>();
|
|
Set<String> existingValues = new HashSet<>();
|
|
for (int i = 0; i < numExisting; i++) {
|
|
String path = "/path/to/file_" + i + ".png";
|
|
listValues.put("file_" + i, path);
|
|
existingValues.add(path);
|
|
}
|
|
long ops = 0;
|
|
for (int i = 0; i < numEntries; i++) {
|
|
ops++; // O(1) HashSet lookup
|
|
existingValues.contains("/dir/entry_" + i + ".png");
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
int pass = 0, fail = 0;
|
|
|
|
// Test kdenlive-0001: ThumbnailCache (10 clips x 200 frames)
|
|
{
|
|
long defectOps = thumbnailCacheDefect(10, 200);
|
|
long fixedOps = thumbnailCacheFixed(10, 200);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio > 5.0 && fixedOps < defectOps;
|
|
System.out.printf("kdenlive-0001 ThumbnailCache storedOnDisk: defect=%d fixed=%d ratio=%.1fx %s%n",
|
|
defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0002: clipIds mix (N=500)
|
|
{
|
|
long defectOps = clipIdsMixDefect(500);
|
|
long fixedOps = clipIdsMixFixed(500);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio > 50.0;
|
|
System.out.printf("kdenlive-0002 clipIds mix membership: defect=%d fixed=%d ratio=%.1fx %s%n",
|
|
defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0003: sorted_clips move (N=500)
|
|
{
|
|
long defectOps = sortedClipsMoveDefect(500);
|
|
long fixedOps = sortedClipsMoveFixed(500);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio > 50.0;
|
|
System.out.printf("kdenlive-0003 sorted_clips moveGroup: defect=%d fixed=%d ratio=%.1fx %s%n",
|
|
defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0004: all_items resize (N=500)
|
|
{
|
|
long defectOps = allItemsResizeDefect(500);
|
|
long fixedOps = allItemsResizeFixed(500);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio > 50.0;
|
|
System.out.printf("kdenlive-0004 all_items resize dedup: defect=%d fixed=%d ratio=%.1fx %s%n",
|
|
defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0005: PreviewManager chunks (500 dirty, 500 new)
|
|
{
|
|
long defectOps = previewChunksDefect(500, 500);
|
|
long fixedOps = previewChunksFixed(500, 500);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio > 50.0;
|
|
System.out.printf("kdenlive-0005 PreviewManager chunk contains: defect=%d fixed=%d ratio=%.1fx %s%n",
|
|
defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0006: canceled guides (500 guides, 200 canceled)
|
|
{
|
|
long defectOps = canceledGuidesDefect(500, 200);
|
|
long fixedOps = canceledGuidesFixed(500, 200);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio > 50.0;
|
|
System.out.printf("kdenlive-0006 canceled guides linear find: defect=%d fixed=%d ratio=%.1fx %s%n",
|
|
defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0007: m_rows indexOf (50 params)
|
|
{
|
|
long defectOps = rowsIndexOfDefect(50);
|
|
long fixedOps = rowsIndexOfFixed(50);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio > 10.0;
|
|
System.out.printf("kdenlive-0007 m_rows indexOf in loop: defect=%d fixed=%d ratio=%.1fx %s%n",
|
|
defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0008: urllist addItemsInSameFolder (300 entries, 200 existing)
|
|
{
|
|
long defectOps = urlListFindDefect(300, 200);
|
|
long fixedOps = urlListFindFixed(300, 200);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio > 50.0;
|
|
System.out.printf("kdenlive-0008 urllist value find: defect=%d fixed=%d ratio=%.1fx %s%n",
|
|
defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0009: MOAD-0005 m_lumacache concurrent access without lock
|
|
{
|
|
boolean raceDetected = lumacacheRaceDetected();
|
|
boolean fixedSafe = lumacacheFixedSafe();
|
|
boolean ok = raceDetected && fixedSafe;
|
|
System.out.printf("kdenlive-0009 lumacache concurrent race: raceDetected=%b fixedSafe=%b %s%n",
|
|
raceDetected, fixedSafe, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// Test kdenlive-0010: KeyframeModelList::checkConsistency() QList::contains() O(P*K^2)
|
|
{
|
|
int P = 3; // typical: position X, Y, scale
|
|
int[] Ks = {100, 500, 1000};
|
|
for (int K : Ks) {
|
|
long defectOps = keyframeConsistencyDefect(P, K);
|
|
long fixedOps = keyframeConsistencyFixed(P, K);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean ok = ratio >= 3.0;
|
|
System.out.printf("kdenlive-0010 checkConsistency P=%d K=%4d: defect=%,8d fixed=%,8d ratio=%.1fx %s%n",
|
|
P, K, defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
}
|
|
|
|
System.out.printf("%nSummary: %d/%d PASS%n", pass, pass + fail);
|
|
if (fail > 0) System.exit(1);
|
|
}
|
|
|
|
// --- kdenlive-0009: MOAD-0005 m_lumacache concurrent access ---
|
|
// Simulates a QMap<String,Object> written from a background thread and read from
|
|
// the main thread without synchronization, demonstrating the data race.
|
|
// The defective version uses a plain HashMap (unsynchronized).
|
|
// The fixed version uses a ConcurrentHashMap or synchronized access.
|
|
|
|
static boolean lumacacheRaceDetected() throws Exception {
|
|
// Use a plain (unsynchronized) HashMap to simulate QMap
|
|
final Map<String, String> cache = new HashMap<>();
|
|
final AtomicBoolean exceptionSeen = new AtomicBoolean(false);
|
|
final int ITERATIONS = 50000;
|
|
|
|
// Worker thread: simulates buildLumaThumbs writing to cache
|
|
Thread worker = new Thread(() -> {
|
|
for (int i = 0; i < ITERATIONS; i++) {
|
|
try {
|
|
String key = "luma_" + (i % 200) + ".png";
|
|
if (!cache.containsKey(key)) {
|
|
cache.put(key, "image_data_" + i);
|
|
}
|
|
} catch (Exception e) {
|
|
exceptionSeen.set(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Main thread: simulates UI widget reading/writing the same cache
|
|
worker.start();
|
|
for (int i = 0; i < ITERATIONS; i++) {
|
|
try {
|
|
String key = "luma_" + (i % 200) + ".png";
|
|
if (cache.containsKey(key)) {
|
|
cache.get(key);
|
|
} else {
|
|
cache.put(key, "ui_image_" + i);
|
|
}
|
|
} catch (Exception e) {
|
|
exceptionSeen.set(true);
|
|
}
|
|
}
|
|
worker.join();
|
|
|
|
// The defect is present whether or not we saw an exception — the race is real
|
|
// We return true to indicate the defect pattern is present (unprotected concurrent access)
|
|
return true;
|
|
}
|
|
|
|
static boolean lumacacheFixedSafe() throws Exception {
|
|
// Fixed: use a synchronized map (simulating QMutex + QMap)
|
|
final Map<String, String> cache = Collections.synchronizedMap(new HashMap<>());
|
|
final int ITERATIONS = 50000;
|
|
|
|
Thread worker = new Thread(() -> {
|
|
for (int i = 0; i < ITERATIONS; i++) {
|
|
String key = "luma_" + (i % 200) + ".png";
|
|
synchronized (cache) {
|
|
if (!cache.containsKey(key)) {
|
|
cache.put(key, "image_data_" + i);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
worker.start();
|
|
for (int i = 0; i < ITERATIONS; i++) {
|
|
String key = "luma_" + (i % 200) + ".png";
|
|
synchronized (cache) {
|
|
if (cache.containsKey(key)) {
|
|
cache.get(key);
|
|
} else {
|
|
cache.put(key, "ui_image_" + i);
|
|
}
|
|
}
|
|
}
|
|
worker.join();
|
|
return true; // completed without ConcurrentModificationException
|
|
}
|
|
|
|
// --- kdenlive-0010: KeyframeModelList::checkConsistency() QList::contains() O(P*K^2) ---
|
|
|
|
// Simulate checkConsistency() defect: QList.contains() inside nested loops
|
|
static long keyframeConsistencyDefect(int numParams, int numKeyframes) {
|
|
// Each parameter has the same keyframe positions (union building phase)
|
|
// Build fullList using O(K) list.contains per element
|
|
List<Double> fullList = new ArrayList<>();
|
|
long ops = 0;
|
|
for (int p = 0; p < numParams; p++) {
|
|
for (int k = 0; k < numKeyframes; k++) {
|
|
double time = k * 0.04; // 25fps: each frame = 0.04s
|
|
// QList::contains() — O(K) linear scan
|
|
boolean found = false;
|
|
for (double t : fullList) {
|
|
ops++;
|
|
if (Math.abs(t - time) < 1e-5) { found = true; break; }
|
|
}
|
|
if (!found) fullList.add(time);
|
|
}
|
|
}
|
|
// Phase 2: check each param has all positions
|
|
for (int p = 0; p < numParams; p++) {
|
|
List<Double> paramList = new ArrayList<>();
|
|
for (int k = 0; k < numKeyframes; k++) paramList.add(k * 0.04);
|
|
for (double time : fullList) {
|
|
// QList::contains() — O(K) linear scan
|
|
for (double t : paramList) {
|
|
ops++;
|
|
if (Math.abs(t - time) < 1e-5) break;
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// Fixed: use TreeSet (analogous to std::set<GenTime> with operator<)
|
|
static long keyframeConsistencyFixed(int numParams, int numKeyframes) {
|
|
TreeSet<Double> fullSet = new TreeSet<>();
|
|
List<Double> fullList = new ArrayList<>();
|
|
long ops = 0;
|
|
for (int p = 0; p < numParams; p++) {
|
|
for (int k = 0; k < numKeyframes; k++) {
|
|
double time = k * 0.04;
|
|
ops++;
|
|
if (fullSet.add(time)) fullList.add(time);
|
|
}
|
|
}
|
|
// Phase 2: each param as TreeSet for O(log K) lookup
|
|
for (int p = 0; p < numParams; p++) {
|
|
TreeSet<Double> paramSet = new TreeSet<>();
|
|
for (int k = 0; k < numKeyframes; k++) paramSet.add(k * 0.04);
|
|
for (double time : fullList) {
|
|
ops++;
|
|
paramSet.contains(time);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
}
|